iOS-深挖BOOL

为啥要深挖这玩意

你每天都在用BOOL吧?那我就来问一道题:请问BOOL是非0即真吗?

如果不是百分百确定的,请往下看。

BOOL的定义(Xcode7.3版本,位于usr/include/objc/objc中)

/// 位于<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_IPHONETARGET_OS_WATCH的定义是在:

TARGET_OS_IPHONE定义

请注意一点,iOS 9.3只是其中的一个SDK,代表的是真机iOS 9.3版本的SDK,在Xcode中有很多的SDK,如:

SDK种类

查看不同的SDK,你会发现TARGET_OS_IPHONETARGET_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上运行该指令):

__LP64__定义1

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__的预编译结果:

__LP64__定义3

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助理器的预编译功能查看这段代码的预编译结果:

YES/NO的定义

经验证,32位与64位真机的预编译结果均如图所示。也就是说当前的iOS系统中,YES与NO的实际类型为__objc_yes__objc_no

2. __objc_yes__objc_no到底又是什么?

但是问题又来了,翻遍usr/include目录,还是找不到__objc_yes的定义,那么它们又是个什么鬼?让我们借助编译器来看看它的类型:

  • 32位下的__objc_yes__objc_no
32位下的__objc_yes
  • 64位下的__objc_yes__objc_no
64位下的__objc_yes

尽管我们无法从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 ");
}
  1. b2的值 : 2
  2. b2 为真
  3. 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?不要告诉我“就是这样的”?如果有人知道可以联系我~(475325435@qq.com

扩展内容

我们来看看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
  1. OBJC_BOOL_IS_BOOL现在默认先判断预编译器的定义。若预编译器未定义该宏,再进行平台判断。

  2. 这里特别强调了TARGET_OS_OSX这个条件,即Mac OS操作系统下,BOOL为signed char类型。其实在早前的Xcode7.3中,TARGET_OS_OSX也是将BOOL定义为signed char,至于苹果在普遍为64位的Mac里不使用bool来作为BOOL的真实类型,还不得而知,如果你知道,可以告诉我~

  3. __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应该是代表手表的宏。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,625评论 0 9
  • 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的...
    lylaut阅读 769评论 0 4
  • 本文转载自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex阅读 711评论 0 1
  • (两年前第一次填词,学习东坡体也不协律) 十年柔情已渺茫 不曾想,怎能忘 一叶扁舟江湖留过往 千载风雨打头墙 再相...
    衣吹风阅读 259评论 3 2
  • 之所以写下如此题目,是想提醒并鞭策自己在新年的第一个春日里重新打造全新的自我。无论从精神面貌还是思想,以及思维方式...
    官小姐不当官阅读 142评论 2 0