一直想搞清楚主線程和主隊列的關系。
其實我一直帶著這么幾個問題。
1.主線程中的任務一定在主隊列中執行嗎?
2.如何保證一定在主線程中執行?
3.如何保證既在主線程中執行又在主隊列中執行?
下面我們帶著這幾個問題來看下面的文章。
先來認識這幾個方法
//給指定的隊列設置標識
dispatch_queue_set_specific(dispatch_queue_t queue, const void *key,
void *_Nullable context, dispatch_function_t _Nullable destructor);
queue:需要關聯的queue,不允許傳入NULL。
key:唯一的關鍵字。
context:要關聯的內容,可以為NULL。
destructor:釋放context的函數,當新的context被設置時,destructor會被調用
//獲取當前所在隊列的標識,根據唯一的key取出當前queue的context,如果當前queue沒有key對應的context,則去queue的target queue取,取不著返回NULL,如果對全局隊列取,也會返回NULL。
dispatch_get_specific(key)
//獲取指定隊列的標識
dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);
第一種情況
//給主隊列設置標識
dispatch_queue_set_specific(dispatch_get_main_queue(), key, @"main", NULL);
//放到同步隊列中 全局并發隊列中
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"main thread: %d", [NSThread isMainThread]);
// 判斷是否是主隊列
void *value = dispatch_get_specific(key);//返回與當前分派隊列關聯的鍵的值。
NSLog(@"main queue: %d", value != NULL);
});
打印的結果:
main thread: 1 //是主線程
main queue: 0 //不是主隊列
分析:不是主隊列是因為 在全局并發隊列中,但是在全局并發隊列中,為何又在主線程執行呢?經過查閱資料發現,蘋果是為了性能,所以在主線程執行,線程切換是耗性能的。
第二種情況
//異步加入到全局并發隊列中
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//異步加入到主隊列中
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"main thread: %d", [NSThread isMainThread]);
NSLog(@"%@",[NSThread currentThread]);
// 判斷是否是主隊列
void *value = dispatch_get_specific(key);//返回與當前分派隊列關聯的鍵的值。
NSLog(@"main queue: %d", value != NULL);
});
});
NSLog(@"dispatch_main會堵塞主線程");
dispatch_main();
NSLog(@"查看是否堵塞主線程");
打印結果:
dispatch_main會堵塞主線程
main thread: 0 //不是主線程
<NSThread: 0x600000b73b80>{number = 3, name = (null)}//不是主線程
main queue: 1 //是主隊列
分析:明明是在dispatch_get_main_queue()中,為何不是在主線程執行呢?是不是很顛覆三觀?原因再 dispatch_main()這個函數。這個函數的作用,經過查閱資料和讀文檔獲取:
/*!
* @function dispatch_main
*
* @abstract
* Execute blocks submitted to the main queue.
* 執行提交給主隊列的任務blocks
*
* @discussion
* This function "parks" the main thread and waits for blocks to be submitted
*
* to the main queue. This function never returns.
*
* Applications that call NSApplicationMain() or CFRunLoopRun() on the
* main thread do not need to call dispatch_main().
*
*/
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NOTHROW DISPATCH_NORETURN
void
dispatch_main(void);
這個函數會阻塞主線程并且等待提交給主隊列的任務blocks完成,這個函數永遠不會返回.
這個方法會阻塞主線程,然后在其它線程中執行主隊列中的任務,這個方法永遠不會返回(意思會卡住主線程).
也就是說,把主隊列中的任務在其他線程中執行。所以用了dispatch_get_main_queue也不一定是主線程的。
加上這個
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"1");
}
我們會發現打印這句話
Attempting to wake up main runloop, but the main thread as exited. This message will only log once. Break on _CFRunLoopError_MainThreadHasExited to debug.
這就完全驗證了dispatch_main()的作用。退出主線程,讓其他線程來執行主線程的任務。
當我們把dispatch_main()注釋掉之后。上面那段代碼的打印
dispatch_main會堵塞主線程
查看是否堵塞主線程
main thread: 1 //是主線程
<NSThread: 0x600001596b80>{number = 1, name = main}//是主線程
main queue: 1//是主隊列
經過上面幾種情況的分析,到底我們需要怎么搞才能確定是保證線程安全的呢?
我查閱了sdwebimage 3.8版本和 4.4.2版本,發現了兩種不同的寫法
3.8版本
#define dispatch_main_sync_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_sync(dispatch_get_main_queue(), block);\
}
#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
4.4.2版本
#ifndef dispatch_queue_async_safe
#define dispatch_queue_async_safe(queue, block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {\
block();\
} else {\
dispatch_async(queue, block);\
}
#endif
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block) dispatch_queue_async_safe(dispatch_get_main_queue(), block)
#endif
那么到底上面兩個版本哪個版本才是最安全的呢?
既然sdwebImage最新版本換了方式,那么肯定,4.2.2是最安全的。
3.8版本是我們平時通常使用的版本,那么對于4.2.2又如何解釋呢?
iOS UI 操作在主線程不一定安全?
通過這篇文章我了解了不少東西。
第一種方案
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
//通過這樣判斷,就可以真正保證(我們在不主動搞事的情況下),任務一定是放在主隊列中的
} else {
// do something in other queue
}
第二種方案 ,sdwebImage的方案
//獲取主隊列名
const char *main_queue_name = dispatch_queue_get_label(dispatch_get_main_queue());
const char *other_queue_name = "other_queue_name";
NSLog(@"\nmain_queue_name====%s", main_queue_name);
//創建一個和主隊列名字一樣的串行隊列
dispatch_queue_t customSerialQueue = dispatch_queue_create(other_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 ");
}
NSLog(@"\nUI Action Finished");
});
} else {
//名字不一樣
NSLog(@"cutomSerialQueue is main queue");
NSLog(@"main thread: %d", [NSThread isMainThread]);
// 判斷是否是主隊列
void *value = dispatch_get_specific(key);//返回與當前分派隊列關聯的鍵的值。
NSLog(@"main queue: %d", value != NULL);
}
總結:我們都知道主隊列是串行隊列,所以串行隊列肯定不會開辟新的線程,也就是說主隊列一定會是在主線程執行。
對于更新UI這種操作,要保證在主線程執行,也就是要保證在主隊列執行。
1.主線程中的任務一定在主隊列中執行嗎?
不是。
2.如何保證一定在主線程中執行?
只要保證在主隊列中執行就可以了。
3.如何保證既在主線程中執行又在主隊列中執行?
保證在主隊列中就會及在主線程又在主隊列。