Objc 中 “== YES” 的愚蠢行為有多可怕
問題引出:
幾個星期前,我遇到一個這樣的bug,在我的機器上用 debug 環境編譯出來的正常執行,但是 RDM 執行出來的總是出現錯誤。當時排查到的問題程式碼大致如下:
- (void)tableFootLoadingViewDidTriggerLoading:(MQZoneTableFootLoadingView *)footLoadingView
{
[self performSelector:@selector(loadMoreData:) withObject:@(YES) afterDelay:1];
}
- (void)loadMoreData:(BOOL)isRefresh
{
if (isRefresh == YES)
{
//...
}
else
{
//...
}
}
大致的排查 bug 情況是我發現無論如何,從 performSelector
進入到的 loadMoreData
的時候,引數 isRefresh
永遠是 NO。
問題解決:
當時,我猜測,這裡 @(YES) 發生了一次把 YES 轉換為 NSNumber
, 然後進入到 loadMoreData
的時候做了一層隱式轉換,變成了 BOOL
型別,並且,這層轉換對於我們來說是一個黑盒子。所以,這裡出錯的可能性極大。
另外, isRefresh
引數和 YES
進行直接比較,這裡的程式碼似乎有點問題。通過修改這兩處地方,bug 得到了很好的解決,修改後的程式碼:
- (void)tableFootLoadingViewDidTriggerLoading:(MQZoneTableFootLoadingView *)footLoadingView
{
[self performSelector:@selector(loadMoreData:) withObject:@(0) afterDelay:1];
}
- (void)loadMoreData:(NSNumber *)refreshNum
{
BOOL isRefresh = [refreshNum integerValue] != 0;
if (isRefresh)
{
}
else
{
}
}
這裡,我修改了兩個地方。
1、引數由 BOOL
改為 NSNumber
, 去除了那層對我們不可見的隱式轉換
2、取消了 isRefresh == YES
的程式碼,改為 if (isRefresh)
問題分析:
在 Objc 中,表示真假的有 BOOL
、bool
、Boolean
, 其實 bool
與 Boolean
均是 C
與 C++
語言更為通用。
三者的區別:
型別 | 定義 | 標頭檔案 | 真 | 假 |
---|---|---|---|---|
bool | _Bool (int) | stdbool.h | true | false |
Boolean | unsigned char | MacTypes.h | TRUE | FALSE |
BOOL | signed char | objc.h | YES | NO |
其中,最大的區別在於 BOOL
被定義為了 signed char
,signed char
的取值範圍為 -127~128。
一:== YES
導致問題
- 測試環境 Xcode 9.1:
下面程式碼輸出了 NO:
int main(int argc, char * argv[])
{
if (2 == YES)
{
NSLog(@"YES");
}
else
{
NSLog(@"NO");
}
}
下面的程式碼輸出 YES
int main(int argc, char * argv[])
{
if (2)
{
NSLog(@"YES");
}
else
{
NSLog(@"NO");
}
}
第二段程式碼輸出 YES
是很顯然的,但是第一段程式碼為何輸出了 NO
, 為此,我們可以輸出 YES, 看結果是啥
NSLog(@"%d", YES); //結果輸出了 1
所以,答案是顯而易見的,2 怎麼可能 == 1 呢,所以 這裡的第一段程式碼輸出了 1。
二:不同機型上的問題
- 測試環境 Xcode 9.1, iPhone 5(注意 5s 為 64位) 與 iPhone 6 模擬器:
下面的程式碼在 32 位機器上 NO, 64 位機器上輸出 YES
int main(int argc, char * argv[])
{
BOOL result = 2;
if (result == YES)
{
NSLog(@"YES");
}
else
{
NSLog(@"NO");
}
}
下面程式碼在 32 位與 64 位機器中,均輸出 YES
int main(int argc, char * argv[])
{
BOOL result = 2;
if (result)
{
NSLog(@"YES");
}
else
{
NSLog(@"NO");
}
}
第二個結果明顯是正確的,但是第一個又是為什麼產生差異呢?
讓我們看看 YES 的定義:
#define OBJC_BOOL_DEFINED
#if __has_feature(objc_bool)
#define YES __objc_yes
#define NO __objc_no
#else
#define YES ((BOOL)1)
#define NO ((BOOL)0)
#endif
首先是巨集 __has_feature(objc_bool)
, 通過下面的程式碼
#if __has_feature(objc_bool)
NSLog(@"YES = __objc_yes");
#else
NSLog(@"YES = 1");
#endif
我發現 32 位 和 64 位機器,都執行了 NSLog(@"YES = __objc_yes");
,也就是說 32 位 和 64 位 YES 都被定義為了 __objc_yes
很遺憾,我沒有找到 __objc_yes
的定義,但是我們可以簡單的把它列印出來看看結果,
NSLog(@"%d", __objc_yes);
輸出結果均為 1
但是,我們通過編譯器的警告,可以看到 __objc_yes
在 32 位和 64 位機器的不同:
32位機器:
![6287298-c3cce2dedd31bbde.png](https://i.iter01.com/images/359dea6303b25361d2205e1a3c98a03c0c1c601e5ea4dc3599cd208ea1b3794a.png)
64位機器:
![6287298-7af90d9554ffc5df.png](https://i.iter01.com/images/acea8bf9ad6bb8978f12cbac6aebc6e8bd6f5d95742c4f3fad7843f5ee3ef128.png)
這就解釋了上面那段程式碼在兩種不同機器上輸出結果不一致的問題了:
在 64 位機器上, __objc_yes
就是 bool 型別的某一個值,那麼在 C++ 中,任何非 0 的值就是 true,所以,在 64 位機器上,result == YES
的程式碼能夠順利執行。
但是在 32 位機器上,__objc_yes
是一個 signed char
,並且 = 1,2 == 1 這個邏輯顯然過不去,所以這裡會導致 32 位和 64 位程式碼的不同執行結果。
但是,到了這裡,我好奇一點:在 64 位機器上,為何 (2 == YES)
無法通過 但是 result = 2; result == YES
卻可以通過呢?
於是,我執行了下面程式碼
BOOL result = 2;
NSLog(@"%d", result);
上述程式碼在 32 位機器上輸出了 2, 在 64 位機器上輸出了 YES, 這也就解釋了上面的問題,也就是說,真正起作用的其實是 BOOL = int 這一層隱式轉換。這一層,對我們來說是黑盒子,而且在 64 位與 32 位機器的表現不一致。
相關文章
- 邦芒忠告:職業生涯中必須避免的12種愚蠢行為
- WWDC 中提到的瀏覽器 Fingerprinting 有多可怕?瀏覽器
- ObjC中的TypeEncodingsOBJEncoding
- 用資料分析網路暴力有多可怕
- ObjC 多執行緒簡析(一)-多執行緒簡述和執行緒鎖的基本應用OBJ執行緒
- 智慧文字Supertext:錯失千萬!標書中的一字之差到底有多可怕?
- ObjC中Category的原理簡析OBJGo
- 維旺迪到底有多可怕,育碧怎樣逃過他的魔爪?
- 人工智慧未來有多可怕?你能預測得到嗎?人工智慧
- 未來人工智慧會有多可怕?你能預測嗎?人工智慧
- 人類擁有智慧和愚蠢兩面,AI要類人,愚蠢是否也必不可少?AI
- ObjC中KVO原理簡析OBJ
- 愚蠢的線上法官
- ObjC中語法糖的趣味應用OBJ
- 阿里web前端面試題到底有多可怕?看完就全明白了!阿里Web前端面試題
- ObjC Runtime簡析-- objc_MsgSendOBJGse
- Eventloop不可怕,可怕的是遇上PromiseOOPPromise
- Swift3、4中的@objc、@objcMembers和dynamicSwiftOBJ
- 剖析 ARM 64 架構中的 objc_msgSend架構OBJGse
- 我們終將洩露的人臉資料,後果到底有多可怕?
- GPT4有那麼可怕嗎?GPT
- 從運營商拿到你的Cookie究竟有多可怕Cookie
- “我想殺死兩個孩子再自殺”產後抑鬱的媽媽有多可怕!
- [譯] 為什麼給設計定義 UX、UI、CX、IA、IxD 和其他型別的頭銜是愚蠢的行為UXUI型別
- 我作為開發者犯過的兩次愚蠢的錯誤
- 可怕的甲醛
- 畫餅成癮的遊戲製作人究竟有多可怕?遊戲
- 在資料可訪問方面避免人為愚蠢 ·Mapflat
- Android中的多程式、多執行緒Android執行緒
- Python中的多工:多執行緒Python執行緒
- ObjC 多執行緒簡析(二)- os_unfair_lock的型別和自旋鎖與互斥鎖的比較OBJ執行緒AI型別
- 何為Java 中的多型?Java多型
- 【API】api 下 session 的 Yes or No?APISession
- JSRE中的多工與多執行緒JS執行緒
- Java中的多執行緒Java執行緒
- [記錄]在高光譜影像分類中遭遇的愚蠢錯誤
- Objc Runtime在專案中該怎麼用OBJ
- 11歲少女叫板支付寶!會寫程式碼的孩子,到底多可怕?
- objc物件說明OBJ物件