为啥要深挖这玩意
你每天都在用BOOL吧?那我就来问一道题:请问BOOL是非0即真吗?
如果不是百分百确定的,请往下看。
BOOL的定义(Xcode7.3版本,位于usr/include/objc/objc中)
/// 位于头文件中
/// Type to represent a boolean value.
#if (TARGET_OS_IPHONE && __LP64__) || TARGET_OS_WATCH
#define OBJC_BOOL_IS_BOOL 1
typedef bool BOOL;
#else
#define OBJC_BOOL_IS_CHAR 1
typedef signed char BOOL;
// BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C"
// even if -funsigned-char is used.
#endif
这是BOOL在SDK中的定义。
1. 让我们展开宏定义来看看:
TARGET_OS_IPHONE
与TARGET_OS_WATCH
的定义是在:
请注意一点,iOS 9.3只是其中的一个SDK,代表的是真机iOS 9.3版本的SDK,在Xcode中有很多的SDK,如:
查看不同的SDK,你会发现TARGET_OS_IPHONE
与TARGET_OS_WATCH
所定义的值是不同的。如iOS 9.3中:
#define TARGET_OS_MAC 1
#define TARGET_OS_WIN32 0
#define TARGET_OS_UNIX 0
#define TARGET_OS_IPHONE 1
#define TARGET_OS_IOS 1
#define TARGET_OS_WATCH 0
#define TARGET_OS_TV 0
#define TARGET_OS_SIMULATOR 0
#define TARGET_OS_EMBEDDED 1
而在OS X 10.11中:
#define TARGET_OS_MAC 1
#define TARGET_OS_WIN32 0
#define TARGET_OS_UNIX 0
#define TARGET_OS_IPHONE 0
#define TARGET_OS_IOS 0
#define TARGET_OS_WATCH 0
#define TARGET_OS_TV 0
#define TARGET_OS_SIMULATOR 0
#define TARGET_OS_EMBEDDED 0
这两个宏定义代表的含义同时也说明了在不同的平台/环境下,部分判断并不是靠“机器”自动进行的,而是靠人为判断并定义的,所以程序并没有那么神奇,它并不能够自动区分iPhone还是iWatch。
__LP64__
则是由预处理器定义的宏,代表当前操作系统是64位。该宏在头文件中无法找到的,但是我们还是有方法可以查看的:
a. 可以在终端中输入cpp -dM /dev/null
进行查看(当然常规手段只能在Mac上运行该指令):
b. 在代码中对__LP64__
进行 if 判断,如:
#if __LP64__
#define kNum 64
#else
#define kNum 32
#endif
NSLog(@"kNum: %d",kNum);
分别选择iPhone5/5c/4s/4等真机和iPhone6/6s等进行运行查看结果(iPhone模拟器也能得出正确值,但非“真实值”)。
c. 通过Xcode助理器的预编译功能查看__LP64__
的预编译结果:
2. 伪代码解释:
if (处于64位iPhone 或者 iWatch) {
BOOL就是bool (非0即真)
} else {
BOOL就是signed char (1个字节。-128 ~ 127)
}
这里的else,最常见的就是32位CPU的iPhone,如iPhone5c/5/4s/4等。
定义里可以看出,BOOL其实是个宏,那么知道了BOOL背后的数据类型,出了问题就有能力去分析问题。
3. 这里就引申出一个可能存在的问题:
- (void)doSomeThingWithA:(NSInteger)a {
NSInteger b = 200;
if (a + b) {
// 业务代码
}
}
乍看之下,这段代码只要 a != -200
,都是能够执行业务代码部分。在64位iPhone和iWatch机器里确实是如此,因为定义的BOOL就是bool,就是非0即真,你用负数也是真。
但是在32位机器里呢?如果a传入56,a + b == 256
,二进制是 0000 0000 0000 0000 0000 0001 0000 0000(因为32位里NSInteger是int,占用4个字节,每个字节8位)。该数值在if时会强转为BOOL(也就是sign char)进行判定:取低8位 0000 0000 来判断,而0000 0000就是0。if中判定为0,则为假,不再执行业务代码。
YES / NO的定义
说到了BOOL,就要说说YES / NO。
#if __has_feature(objc_bool)
#define YES __objc_yes
#define NO __objc_no
#else
#define YES ((BOOL)1)
#define NO ((BOOL)0)
#endif
这两玩意也是宏。这里定义了YES / NO在不同条件下的真实类型。
1. 让我们展开宏定义来看看:
__has_feature
这个宏是clang提供的一个宏,用来指定在当前语言下是否支持某个“特征”。上述的objc_bool
这个特征暂时还没有找到具体的定义,因此让我们来用代码检测下__has_feature(objc_bool)
这个条件是否成立。
#if __has_feature(objc_bool)
#define kHaveFeature 10
#else
#define kHaveFeature 20
#endif
NSLog(@"kHaveFeature : %d", kHaveFeature);
NSLog(@"__objc_yes : %d",__objc_yes);
NSLog(@"__objc_no : %d",__objc_no);
NSLog(@"YES : %d",YES);
NSLog(@"NO : %d",NO);
让我们用Xcode助理器的预编译功能查看这段代码的预编译结果:
经验证,32位与64位真机的预编译结果均如图所示。也就是说当前的iOS系统中,YES与NO的实际类型为__objc_yes
与__objc_no
。
2. __objc_yes
和__objc_no
到底又是什么?
但是问题又来了,翻遍usr/include目录,还是找不到__objc_yes
的定义,那么它们又是个什么鬼?让我们借助编译器来看看它的类型:
- 32位下的
__objc_yes
与__objc_no
- 64位下的
__objc_yes
与__objc_no
尽管我们无法从SDK的头文件里获取__objc_yes
与__objc_no
的真实类型,但是我们的编译器还是帮助我们找到了它们的真实类型:BOOL。
再让我们实际打印出这两者的值来看看:
NSLog(@"__objc_yes : %d",__objc_yes);
NSLog(@"__objc_no : %d",__objc_no);
NSLog(@"YES : %d",YES);
NSLog(@"NO : %d",NO);
// 结果:32位与64位均为:
2016-12-29 20:15:20.321 NewTestBOOL[3574:641831] __objc_yes : 1
2016-12-29 20:15:20.322 NewTestBOOL[3574:641831] __objc_no : 0
2016-12-29 20:15:20.324 NewTestBOOL[3574:641831] YES : 1
2016-12-29 20:15:20.325 NewTestBOOL[3574:641831] NO : 0
3. 这里又引申出一个可能存在的问题:
请问下列代码会打印哪些结果?
BOOL b2 = 2;
NSLog(@"b2 的值 : %d",b2);
if (b2) {
NSLog(@"b2 为真");
} else {
NSLog(@"b2 为假");
}
if (b2 == YES) {
NSLog(@"b2 等于 YES ");
} else {
NSLog(@"b2 不等于 YES ");
}
- b2的值 : 2
- b2 为真
- b2 不等于 YES
这个答应在32位与64位下应该没问题了~那让我们实际跑起来看看:
-
32位下:
2016-12-29 20:44:08.515 NewTestBOOL[3600:645491] b2 的值 : 2 2016-12-29 20:44:08.516 NewTestBOOL[3600:645491] b2 为真 2016-12-29 20:44:08.517 NewTestBOOL[3600:645491] b2 不等于 YES
没毛病~
64位下:
2016-12-29 20:45:03.546554+0800 NewTestBOOL[3337:1547022] b2 的值 : 1
2016-12-29 20:45:03.546564+0800 NewTestBOOL[3337:1547022] b2 为真
2016-12-29 20:45:03.546594+0800 NewTestBOOL[3337:1547022] b2 等于 YES
有毛病!
让我们仔细分析下64位为何b2会变成1:
64位下b2的类型是BOOL,真实类型为bool,任何不为0的数强转为bool类型,均为转为true,在C99的官方定义中,true为1,所以这里b2变为了1。当然这里还有个疑问:为何不为0的数转为bool均为true?不要告诉我“就是这样的”?如果有人知道可以联系我~([email protected])
扩展内容
我们来看看C99中的bool定义:
#ifndef __STDBOOL_H
#define __STDBOOL_H
/* Don't define bool, true, and false in C++, except as a GNU extension. */
#ifndef __cplusplus
#define bool _Bool
#define true 1
#define false 0
#elif defined(__GNUC__) && !defined(__STRICT_ANSI__)
/* Define _Bool, bool, false, true as a GNU extension. */
#define _Bool bool
#define bool bool
#define false false
#define true true
#endif
#define __bool_true_false_are_defined 1
#endif /* __STDBOOL_H */
这是stdbool.h中的bool的定义(C99标准)。
bool其实还是个宏,其真实类型为_Bool
。_Bool
在C99中是作为C语言原生的布尔类型的,也就是和int/float一样是在编译器中预定义的,1为真,0为假,具体占用内存大小还是由编译器决定。定义中的true与false分别为1和0。
总结
宏真的是个好东西~
更新:2017年4月16日
当前最新的iOS版本为10.3.1,Xcode最新版本为8.3.1。
很遗憾Steve Jobs的经典iPhone4在默认的Xcode8中无法真机调试(连线调试),但是还是可以打包进行安装运行的,只不过可能查看日志略麻烦~需要支持iOS7的同学们可能要多费些力。
当前10.3 SDK中已经对BOOL的定义作了少许改动,让我们来瞧瞧:
/// Type to represent a boolean value.
#if defined(__OBJC_BOOL_IS_BOOL)
// Honor __OBJC_BOOL_IS_BOOL when available.
# if __OBJC_BOOL_IS_BOOL
# define OBJC_BOOL_IS_BOOL 1
# else
# define OBJC_BOOL_IS_BOOL 0
# endif
#else
// __OBJC_BOOL_IS_BOOL not set.
# if TARGET_OS_OSX || (TARGET_OS_IOS && !__LP64__ && !__ARM_ARCH_7K)
# define OBJC_BOOL_IS_BOOL 0
# else
# define OBJC_BOOL_IS_BOOL 1
# endif
#endif
#if OBJC_BOOL_IS_BOOL
typedef bool BOOL;
#else
# define OBJC_BOOL_IS_CHAR 1
typedef signed char BOOL;
// BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C"
// even if -funsigned-char is used.
#endif
OBJC_BOOL_IS_BOOL
现在默认先判断预编译器的定义。若预编译器未定义该宏,再进行平台判断。这里特别强调了
TARGET_OS_OSX
这个条件,即Mac OS操作系统下,BOOL为signed char类型。其实在早前的Xcode7.3中,TARGET_OS_OSX
也是将BOOL定义为signed char,至于苹果在普遍为64位的Mac里不使用bool来作为BOOL的真实类型,还不得而知,如果你知道,可以告诉我~-
__ARM_ARCH_7K
这个宏,应该是在Clang和LLVM中定义的。搜索LLVM4.0源码,能得到的信息只有:// Unfortunately, __ARM_ARCH_7K__ is now more of an ABI descriptor. The CPU // happens to be Cortex-A7 though, so it should still get __ARM_ARCH_7A__. if (getTriple().isWatchABI()) Builder.defineMacro("__ARM_ARCH_7K__", "2");
据悉iWatch的CPU指令集为armv7k,由该定义推测,
__ARM_ARCH_7K
应该是代表手表的宏。