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 中,表示真假的有 BOOLboolBoolean, 其實 boolBoolean 均是 CC++ 語言更為通用。

三者的區別:

類型 定義 頭文件
bool _Bool (int) stdbool.h true false
Boolean unsigned char MacTypes.h TRUE FALSE
BOOL signed char objc.h YES NO

其中,最大的區別在于 BOOL 被定義為了 signed charsigned 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位機器:


32位機器.png

64位機器:


64位機器.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 位機器的表現不一致。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,757評論 0 9
  • 為啥要深挖這玩意 你每天都在用BOOL吧?那我就來問一道題:請問BOOL是非0即真嗎? 如果不是百分百確定的,請往...
    從來吃不胖閱讀 3,146評論 3 50
  • 作者 : 韓曙亮轉載請注明出處 : http://blog.csdn.net/shulianghan/articl...
    NieFeng1024閱讀 1,523評論 0 2
  • 數一數,十五天了,放假回來半個月了 頹廢了半個月了 心也迷失了,開始彷徨了,厚厚的陰霾蒙住了我的眼,讓我抬不起頭看...
    馳而不息的小貝殼閱讀 138評論 0 0
  • 《唯愛可愛》 再不相信、世間有愛 將自己淹沒在人海 感受生命渺小如塵埃 如此或許、不會再受傷害 流連鬧市、喧囂徘徊...
    深海魚兒_玉閱讀 423評論 0 2