問題
最近在看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

由此,我們可以大膽的猜測,蘋果的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é)果:

方案二
采用 dispatch_queue_set_specific 和 dispatch_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),輕噴~~