iOS:GCD【dispatch_semaphore】使用之所思
最近在使用GCD的時候,發現自己對dispatch_semaphore
理解不是那麼深刻,所以自己在網上找資料學習dispatch_semaphore
,並對dispatch_semaphore
有了一些自己的看法和理解。
1.非同步任務使用semaphore
NSLog(@"----------------------------開始----------------------------執行緒:%@",[NSThread currentThread]); dispatch_semaphore_t sema =dispatch_semaphore_create(0); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"---------------------------- 任務一開始 ---------------------------- 執行緒:%@",[NSThread currentThread]); [NSThread sleepForTimeInterval:2.0]; NSLog(@"---------------------------- 任務一結束 ---------------------------- 執行緒:%@",[NSThread currentThread]); dispatch_semaphore_signal(sema); }); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"---------------------------- 任務二開始 ---------------------------- 執行緒:%@",[NSThread currentThread]); [NSThread sleepForTimeInterval:2.0]; NSLog(@"---------------------------- 任務二結束 ---------------------------- 執行緒:%@",[NSThread currentThread]); dispatch_semaphore_signal(sema); }); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"---------------------------- 任務三開始 ---------------------------- 執行緒:%@",[NSThread currentThread]); [NSThread sleepForTimeInterval:2.0]; NSLog(@"---------------------------- 任務三結束 ---------------------------- 執行緒:%@",[NSThread currentThread]); dispatch_semaphore_signal(sema); }); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); NSLog(@"----------------------------結束---------------------------- 執行緒:%@",[NSThread currentThread]);
列印結果如下:
2018-11-20 10:05:48.165624+0800 Demo[2920:724653] ----------------------------開始----------------------------執行緒:<NSThread: 0x282c6b180>{number = 1, name = main} 2018-11-20 10:05:48.166227+0800 Demo[2920:724705] ---------------------------- 任務一開始 ---------------------------- 執行緒:<NSThread: 0x282cebc40>{number = 3, name = (null)} 2018-11-20 10:05:50.171470+0800 Demo[2920:724705] ---------------------------- 任務一結束 ---------------------------- 執行緒:<NSThread: 0x282cebc40>{number = 3, name = (null)} 2018-11-20 10:05:50.171953+0800 Demo[2920:724705] ---------------------------- 任務二開始 ---------------------------- 執行緒:<NSThread: 0x282cebc40>{number = 3, name = (null)} 2018-11-20 10:05:52.177195+0800 Demo[2920:724705] ---------------------------- 任務二結束 ---------------------------- 執行緒:<NSThread: 0x282cebc40>{number = 3, name = (null)} 2018-11-20 10:05:52.177657+0800 Demo[2920:724710] ---------------------------- 任務三開始 ---------------------------- 執行緒:<NSThread: 0x282ce9380>{number = 4, name = (null)} 2018-11-20 10:05:54.182973+0800 Demo[2920:724710] ---------------------------- 任務三結束 ---------------------------- 執行緒:<NSThread: 0x282ce9380>{number = 4, name = (null)} 2018-11-20 10:05:54.183224+0800 Demo[2920:724653] ----------------------------結束---------------------------- 執行緒:<NSThread: 0x282c6b180>{number = 1, name = main}
這裡我們可以分析到,semaphore確實起到了阻塞等待的作用。dispatch_semaphore_t sema = dispatch_semaphore_create(0)
首先建立了一個訊號量為0的semaphore,接著開啟了一個非同步任務,在任務中dispatch_semaphore_signal(sema)
會使訊號量+1,然後在主執行緒中dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)
使訊號量-1,如果此時訊號量為0,那麼會一直等待。所以主執行緒會一直等待,直到非同步任務執行完畢dispatch_semaphore_signal(sema)
這裡,對訊號量+1,然後才會接著往下走。後面的任務以此類推。
可是在我們平常的使用中,更多是配合網路請求。例如請求B的資料依賴於請求A,只有先拿到了請求A的資料,才能請求B,當然這個需求解決辦法有很多,但這裡我們就只談談怎麼用semaphore來解決這個需求。接著我們利用相同思路,結合AFN使用semaphore:
NSLog(@"----------------- 測試開始 -----------------"); NSDictionary *dict = @{ @"page": @1, @"pageSize": @20}; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); //對AFN的簡單封裝 請求一個post任務 [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypePOST withURLString:@"https://api.it120.cc/tz/shop/goods/list" withParameters:dict withSuccessBlock:^(id result) { NSLog(@"----------------- 任務一完成 -----------------%@ -----------------",[NSThread currentThread]); dispatch_semaphore_signal(semaphore); } withFailBlock:^(NSError *error) { dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //請求一個get任務 [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) { NSLog(@"----------------- 任務二完成 ----------------- %@ -----------------",[NSThread currentThread]); dispatch_semaphore_signal(semaphore); } withFailBlock:^(NSError *error) { dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"----------------- 測試結束 -----------------");
列印結果如下:
2018-11-20 10:05:01.794640+0800 Demo[2918:724320] ----------------- 測試開始 -----------------
這裡我們發現,僅僅只列印了測試開始
,後面的任務都沒有執行。那麼究竟是什麼原因,導致了主執行緒的阻塞呢?原來在AFN中,AFN已經已經將回調回到了主執行緒中:
[[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) { NSLog(@"----------------- 當前執行緒:%@ -----------------",[NSThread currentThread]); } withFailBlock:^(NSError *error) { }]; 列印結果如下: 2018-11-20 10:11:32.721403+0800 Demo[2924:725812] ----------------- 當前執行緒:<NSThread: 0x283e4fa00>{number = 1, name = main} -----------------
而在執行到第一個dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
時候,訊號量就為0了,將主執行緒阻塞了,就沒有辦法繼續往下執行dispatch_semaphore_signal(semaphore)
。那為什麼上面第一種方法可以執行呢?是因為上面的任務都是非同步的,所以就算主執行緒阻塞了,在非同步任務中還是可以執行dispatch_semaphore_signal(semaphore)
,也就不存在阻塞問題了。
那麼我們怎麼去解決這樣的問題呢?這裡我們就需要用到GCD的另外一種使用方式了:dispatch_group
。對dispatch_group
不是很瞭解的小夥伴可以參考這裡,裡面對GCD的各種使用方法都有很詳細的講解。
好了,廢話不多說,接下來我們看看怎麼用dispatch_group
完成dispatch_semaphore
和AFN的結合使用:
2.結合AFN使用dispatch_semaphore
NSLog(@"----------------- 測試開始 -----------------"); dispatch_group_t gruop = dispatch_group_create(); dispatch_group_async(gruop, dispatch_get_global_queue(0, 0), ^{ dispatch_semaphore_t sema = dispatch_semaphore_create(0); NSLog(@"----------------- gruop:%@ -----------------",[NSThread currentThread]); [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) { NSLog(@"----------------- 任務一完成 ----------------- %@ -----------------",[NSThread currentThread]); dispatch_semaphore_signal(sema); } withFailBlock:^(NSError *error) { dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) { NSLog(@"----------------- 任務二完成 ----------------- %@ -----------------",[NSThread currentThread]); dispatch_semaphore_signal(sema); } withFailBlock:^(NSError *error) { dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) { NSLog(@"----------------- 任務三完成 ----------------- %@ -----------------",[NSThread currentThread]); dispatch_semaphore_signal(sema); } withFailBlock:^(NSError *error) { dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); }); dispatch_group_notify(gruop, dispatch_get_main_queue(), ^{ NSLog(@"----------------- 測試結束 -----------------"); });
列印結果如下:
2018-11-20 10:31:07.634295+0800 Demo[2942:730510] ----------------- 測試開始 ----------------- 2018-11-20 10:31:07.636867+0800 Demo[2942:730582] ----------------- gruop:<NSThread: 0x283c8ebc0>{number = 3, name = (null)} ----------------- 2018-11-20 10:31:08.057232+0800 Demo[2942:730510] ----------------- 任務一完成 ----------------- <NSThread: 0x283c01b40>{number = 1, name = main} ----------------- 2018-11-20 10:31:08.130937+0800 Demo[2942:730510] ----------------- 任務二完成 ----------------- <NSThread: 0x283c01b40>{number = 1, name = main} ----------------- 2018-11-20 10:31:08.229891+0800 Demo[2942:730510] ----------------- 任務三完成 ----------------- <NSThread: 0x283c01b40>{number = 1, name = main} ----------------- 2018-11-20 10:31:08.230509+0800 Demo[2942:730510] ----------------- 測試結束 -----------------
根據列印的結果,這裡完美解決了A任務、B任務之間的依賴關係的需求。也再次證明AFN的回撥確實在主執行緒中(AFN的原始碼中,也可以找到,有興趣的小夥伴可以研究下原始碼)。
那麼,為什麼用到dispatch_group
可以解決問題呢?這裡dispatch_group_async(gruop, dispatch_get_global_queue(0, 0)
開闢一個新的子執行緒。而在子執行緒中,dispatch_semaphore_t sema = dispatch_semaphore_create(0)
建立了一個訊號量為0的semaphore,緊接著在當前子執行緒中執行dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)
,那麼會阻塞當前執行緒,一直等待,直到等待AFN的回撥中dispatch_semaphore_signal(sema)
將訊號量+1。
如果對以上理解了的話,同樣開啟一個非同步任務,也可以將任務一、二、三按順序完成:
NSLog(@"----------------- 測試開始 -----------------"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ dispatch_semaphore_t sema = dispatch_semaphore_create(0); [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) { NSLog(@"----------------- 任務一完成 ----------------- %@ -----------------",[NSThread currentThread]); dispatch_semaphore_signal(sema); } withFailBlock:^(NSError *error) { dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) { NSLog(@"----------------- 任務二完成 ----------------- %@ -----------------",[NSThread currentThread]); dispatch_semaphore_signal(sema); } withFailBlock:^(NSError *error) { dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); [[CYW_NetworkingManager shareManager] cyw_networkType:NetWorkTypeGET withURLString:@"https://api.it120.cc/tz/shop/goods/category/all" withParameters:nil withSuccessBlock:^(id result) { NSLog(@"----------------- 任務三完成 ----------------- %@ -----------------",[NSThread currentThread]); dispatch_semaphore_signal(sema); } withFailBlock:^(NSError *error) { dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); }); NSLog(@"----------------- 測試結束 -----------------");
列印結果如下:
2018-11-20 10:38:48.473069+0800 Demo[2950:732087] ----------------- 測試開始 ----------------- 2018-11-20 10:38:48.473251+0800 Demo[2950:732087] ----------------- 測試結束 ----------------- 2018-11-20 10:38:48.917159+0800 Demo[2950:732087] ----------------- 任務一完成 ----------------- <NSThread: 0x282351b40>{number = 1, name = main} ----------------- 2018-11-20 10:38:49.005818+0800 Demo[2950:732087] ----------------- 任務二完成 ----------------- <NSThread: 0x282351b40>{number = 1, name = main} ----------------- 2018-11-20 10:38:49.092094+0800 Demo[2950:732087] ----------------- 任務三完成 ----------------- <NSThread: 0x282351b40>{number = 1, name = main} -----------------
這裡可以看到,雖然並不像使用dispatch_gruop
那樣,將排程組中的任務,執行完畢後再去呼叫
dispatch_group_notify
裡面的任務。但任務一、二、三卻也是按順序執行的。
以上是我個人對dispatch_semaphore
的一點點理解,有錯誤的地方希望大家能指出來。
最後,個人還有點小疑問。既然AFN的回撥已經在主執行緒中了,那麼為什麼我們還要AFN的回撥中用dispatch_get_main_queue()
回到主執行緒更新UI呢?