為啥要深挖這玩意
你每天都在用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_IPHONE
與TARGET_OS_WATCH
的定義是在:
請注意一點饲鄙,iOS 9.3只是其中的一個SDK,代表的是真機iOS 9.3版本的SDK圆雁,在Xcode中有很多的SDK忍级,如:
查看不同的SDK,你會發(fā)現(xiàn)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
這兩個宏定義代表的含義同時也說明了在不同的平臺/環(huán)境下轴咱,部分判斷并不是靠“機器”自動進行的,而是靠人為判斷并定義的烈涮,所以程序并沒有那么神奇朴肺,它并不能夠自動區(qū)分iPhone還是iWatch。
__LP64__
則是由預(yù)處理器定義的宏坚洽,代表當前操作系統(tǒng)是64位戈稿。該宏在頭文件中無法找到的,但是我們還是有方法可以查看的:
a. 可以在終端中輸入cpp -dM /dev/null
進行查看(當然常規(guī)手段只能在Mac上運行該指令):
b. 在代碼中對__LP64__
進行 if 判斷讶舰,如:
#if __LP64__
#define kNum 64
#else
#define kNum 32
#endif
NSLog(@"kNum: %d",kNum);
分別選擇iPhone5/5c/4s/4等真機和iPhone6/6s等進行運行查看結(jié)果(iPhone模擬器也能得出正確值鞍盗,但非“真實值”)。
c. 通過Xcode助理器的預(yù)編譯功能查看__LP64__
的預(yù)編譯結(jié)果:
2. 偽代碼解釋:
if (處于64位iPhone 或者 iWatch) {
BOOL就是bool (非0即真)
} else {
BOOL就是signed char (1個字節(jié)跳昼。-128 ~ 127)
}
這里的else般甲,最常見的就是32位CPU的iPhone,如iPhone5c/5/4s/4等庐舟。
定義里可以看出欣除,BOOL其實是個宏,那么知道了BOOL背后的數(shù)據(jù)類型挪略,出了問題就有能力去分析問題历帚。
3. 這里就引申出一個可能存在的問題:
- (void)doSomeThingWithA:(NSInteger)a {
NSInteger b = 200;
if (a + b) {
// 業(yè)務(wù)代碼
}
}
乍看之下,這段代碼只要 a != -200
杠娱,都是能夠執(zhí)行業(yè)務(wù)代碼部分挽牢。在64位iPhone和iWatch機器里確實是如此,因為定義的BOOL就是bool摊求,就是非0即真禽拔,你用負數(shù)也是真。
但是在32位機器里呢?如果a傳入56睹栖,a + b == 256
硫惕,二進制是 0000 0000 0000 0000 0000 0001 0000 0000(因為32位里NSInteger是int,占用4個字節(jié)野来,每個字節(jié)8位)恼除。該數(shù)值在if時會強轉(zhuǎn)為BOOL(也就是sign char)進行判定:取低8位 0000 0000 來判斷,而0000 0000就是0曼氛。if中判定為0豁辉,則為假,不再執(zhí)行業(yè)務(wù)代碼舀患。
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助理器的預(yù)編譯功能查看這段代碼的預(yù)編譯結(jié)果:
經(jīng)驗證努咐,32位與64位真機的預(yù)編譯結(jié)果均如圖所示苦蒿。也就是說當前的iOS系統(tǒng)中,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);
// 結(jié)果: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. 這里又引申出一個可能存在的問題:
請問下列代碼會打印哪些結(jié)果?
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
這個答應(yīng)在32位與64位下應(yīng)該沒問題了~那讓我們實際跑起來看看:
-
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的數(shù)強轉(zhuǎn)為bool類型弄跌,均為轉(zhuǎn)為true,在C99的官方定義中尝苇,true為1铛只,所以這里b2變?yōu)榱?埠胖。當然這里還有個疑問:為何不為0的數(shù)轉(zhuǎn)為bool均為true?不要告訴我“就是這樣的”淳玩?如果有人知道可以聯(lián)系我~(475325435@qq.com)
擴展內(nèi)容
我們來看看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一樣是在編譯器中預(yù)定義的,1為真侮东,0為假,具體占用內(nèi)存大小還是由編譯器決定豹芯。定義中的true與false分別為1和0悄雅。
總結(jié)
宏真的是個好東西~
更新:2017年4月16日
當前最新的iOS版本為10.3.1,Xcode最新版本為8.3.1铁蹈。
很遺憾Steve Jobs的經(jīng)典iPhone4在默認的Xcode8中無法真機調(diào)試(連線調(diào)試)宽闲,但是還是可以打包進行安裝運行的,只不過可能查看日志略麻煩~需要支持iOS7的同學們可能要多費些力握牧。
當前10.3 SDK中已經(jīng)對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
現(xiàn)在默認先判斷預(yù)編譯器的定義。若預(yù)編譯器未定義該宏沿腰,再進行平臺判斷览徒。這里特別強調(diào)了
TARGET_OS_OSX
這個條件,即Mac OS操作系統(tǒng)下颂龙,BOOL為signed char類型习蓬。其實在早前的Xcode7.3中,TARGET_OS_OSX
也是將BOOL定義為signed char措嵌,至于蘋果在普遍為64位的Mac里不使用bool來作為BOOL的真實類型躲叼,還不得而知,如果你知道企巢,可以告訴我~-
__ARM_ARCH_7K
這個宏枫慷,應(yīng)該是在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");
據(jù)悉iWatch的CPU指令集為armv7k或听,由該定義推測,
__ARM_ARCH_7K
應(yīng)該是代表手表的宏罗丰。