iOS UI 操作在主線程不一定安全?

問題

最近在看SDWebImage的時候看到了他如何強行保護 UI 操作放置在主線程中執(zhí)行,代碼如下:

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
    if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
#endif

頓時心生疑問,按照我自己的寫法,不應(yīng)該這樣么:

if ([NSThread isMainThread]) {
       block();
} else {
   dispatch_async(dispatch_get_main_queue(), ^{
       block();
   })
}

在查閱一陣子之后,沒想到居然是真的。。。在 ReactiveCocoa 的一個 issue里提到在MapKit 中的 MKMapView 有個 addOverlay 方法,這個方法不僅要在主線程中執(zhí)行,而且要把這個操作加到主隊列中才可以。并且后來 Apple DTS 也承認了這是一個bug

ADTS Bug

由此,我們可以大膽的猜測,蘋果的API可能是問題的,我們得想一個更加安全的方式規(guī)避這種即使有此類bug,萬一別的API也有這樣的問題也不至于導(dǎo)致APP出問題

解決方案

我們知道,在主隊列中的任務(wù),一定會放到主線程執(zhí)行; 所以只要是在主隊列中的任務(wù),既可以保證在主隊列,也可以保證在主線程中執(zhí)行。所以咱們就可以通過判斷當(dāng)前隊列是不是主隊列來代替判斷當(dāng)前執(zhí)行任務(wù)的線程是否是主線程,這樣更加安全!

方案一:
我們知道在使用 GCD 創(chuàng)建一個 queue 的時候會指定 queue_label,可以理解為隊列名,就像下面:

dispatch_queue_t myQueue = dispatch_queue_create("com.apple.threadQueue", DISPATCH_QUEUE_SERIAL);

而第一個參數(shù)就是 queue_label,根據(jù)官方文檔解釋,這個queueLabel應(yīng)該是唯一的,所以SD就采用了這個方式

   //取得當(dāng)前隊列的隊列名
   dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)
   
   //取得主隊列的隊列名
   dispatch_queue_get_label(dispatch_get_main_queue())
   
   然后通過 strcmp 函數(shù)進行比較,如果為0 則證明當(dāng)前隊列就是主隊列。

正常情況下是可以這樣來判斷當(dāng)前隊列是不是主隊列的,但考慮下面一種情況


//我定義了一個和主隊列一樣隊列名的隊列,通過這個判斷,很明顯判斷成了主隊列,于是我在里面做UI操作。

- (void)testQueue {
    //獲取主隊列名
    const char *main_queue_name = dispatch_queue_get_label(dispatch_get_main_queue());
    NSLog(@"\nmain_queue_name====%s", main_queue_name);
    //創(chuàng)建一個和主隊列名字一樣的串行隊列
    dispatch_queue_t customSerialQueue = dispatch_queue_create(main_queue_name, DISPATCH_QUEUE_SERIAL);
    if (strcmp(dispatch_queue_get_label(customSerialQueue), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {
        //名字一樣
        NSLog(@"\ncutomSerialQueue is main queue");
        dispatch_async(customSerialQueue, ^{
            //將更新UI的操作放到這個隊列
            if ([NSThread isMainThread]) {
                NSLog(@"i am mainThread ");
            }
            UIImageView *imageView = [[UIImageView alloc] init];
            [self.view addSubview:imageView];
            self.view.frame = CGRectMake(0, 0, 50, 50);
            self.view.backgroundColor = [UIColor greenColor];
            NSLog(@"\nUI Action Finished");
        });
        
    } else {
        //名字不一樣
        NSLog(@"cutomSerialQueue is main queue");
    }
}

所以,我想表達,如果我定義了一個這樣的隊列,并且當(dāng)前隊列就是這個隊列,然后我再把 SD 設(shè)置圖片的操作加到這個隊列里面,這樣會不會導(dǎo)致 SD 誤判了,導(dǎo)致程序出問題,雖然這樣很極限。如果是我理解錯了,還請大神們悉心提出,求輕噴~~~~

執(zhí)行結(jié)果:


result

方案二
采用 dispatch_queue_set_specificdispatch_get_specific 這一組方法為隊列綁定標記,后面再取標記對比;當(dāng)然你在這樣的情況下,就別把自己的隊列也和主隊列打同樣的標記了,不然就是在搞事情了。。。。

// 通過設(shè)置key/value數(shù)據(jù)與指定的queue進行關(guān)聯(lián)。
dispatch_queue_set_specific(dispatch_queue_t queue, const void *key,
void *_Nullable context, dispatch_function_t _Nullable destructor);
//參數(shù):
queue:需要關(guān)聯(lián)的queue,不允許傳入NULL。
key:唯一的關(guān)鍵字。
context:要關(guān)聯(lián)的內(nèi)容,可以為NULL。
destructor:釋放context的函數(shù),當(dāng)新的context被設(shè)置時,destructor會被調(diào)用

// 根據(jù)唯一的key取出當(dāng)前queue的context,如果當(dāng)前queue沒有key對應(yīng)的context,則去queue的target queue取,取不著返回NULL,如果對全局隊列取,也會返回NULL。
dispatch_get_specific(const void *key)

//參數(shù)
key:當(dāng)時設(shè)置的關(guān)鍵字。

使用方式:

- (void)function {
    static void *mainQueueKey = "mainQueueKey";
    dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, &mainQueueKey, NULL);
    if (dispatch_get_specific(mainQueueKey)) {
        // do something in main queue
        //通過這樣判斷,就可以真正保證(我們在不主動搞事的情況下),任務(wù)一定是放在主隊列中的
    } else {
        // do something in other queue
    }
}

總結(jié)

本文就從閱讀 SD 源碼中的一段代碼而引發(fā)出的對主隊列,主線程的一些思考;如有不正確,還請大神指導(dǎo),輕噴~~

參考資料

主線程中也不絕對安全的 UI 操作
GCD's Main Queue vs. Main Thread

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容