前段時間學習了一下GCD相關的知識,看了不少同學寫的Blog,通過使用dispatch_group
和dispatch_semaphore
解決網路請求同步、非同步問題,讓我受益良多,也激發了我的思考。
我提出了一種問題場景
多請求非同步發起,要求其回撥按順序執行。
題目很簡單,為了能方便大家的理解,我舉個栗子:A、B、C三個網路請求,一同發出,當然它們請求回來的時間肯定是不一致的,但是我希望三個網路請求對應的blockA、blockB、blockC是按照順序執行的。
也就是說就算網路請求B回來了,但是A沒回來,blockB也要等著A請求的blockA先執行完才能執行。
我想我的問題已經描述的很清楚了,不知道讀到這裡同學們有沒有想法?
可以思考一下,當然!其實你使用各種識別符號控制、i++計量、請求任務的Block中巢狀另一個請求任務的發起動作等……方法是可以解決這種場景問題。但是!既然是為了學習,就不要把為了規避問題當做第一要務。
接下來我說一下我的處理辦法,可能不是最好的辦法,也就是為大家提供一個思路。
1. 首先我們模擬一下網路請求
此處模擬網路求情寫的有點複雜了,之所以加到並行佇列中是想體現這三個網路請求是非同步併發的。
dispatch_queue_t requestQueue = dispatch_queue_create("com.requestQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(requestQueue, ^{
// 模擬第一個網路請求requestA,4s後返回
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 執行requestA請求完成後的taskA
});
});
dispatch_async(requestQueue, ^{
// 模擬第一個網路請求requestB,2s後返回
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 執行requestB請求完成後的taskB
});
});
dispatch_async(requestQueue, ^{
// 模擬第一個網路請求requestA,3s後返回
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 執行requestC請求完成後的taskC
});
});
複製程式碼
2. 保證順序執行回撥
為了能順序執行回撥方法,我想到了序列佇列,如果我把blockA/B/C
都丟到一個序列佇列中,那麼就可以保證回撥處理是順序執行的了,所以有了下面的程式碼:
dispatch_queue_t blockQueue = dispatch_queue_create("com.blockQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(blockQueue, ^{
[self taskA];
});
dispatch_sync(blockQueue, ^{
[self taskB];
});
dispatch_sync(blockQueue, ^{
[self taskC];
});
複製程式碼
我將請求後需要執行的task
預先加入到了一個序列佇列中。
3. 控制啟動的時機
任何一個task
的執行都有兩個條件約束:
- 已執行優先順序更高的
task
; task
本身對應的request
已請求完成;
既然序列佇列保證了第1點:我們的執行順序,我們在加上第二點約束即可。
我選擇了使用訊號量來通知請求是否完成,並能夠使未完成的請求所對應的task
保持等待狀態!簡直完美。
建立了3個訊號量的例項
初始化訊號量為0。
@property (nonatomic, strong) dispatch_semaphore_t semaA;
@property (nonatomic, strong) dispatch_semaphore_t semaB;
@property (nonatomic, strong) dispatch_semaphore_t semaC;
複製程式碼
修改上面虛擬網路請求的程式碼
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t requestQueue = dispatch_queue_create("com.requestQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(requestQueue, ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 通過訊號量通知
// 執行requestA請求完成後的TaskA
dispatch_semaphore_signal(self.semaA);
});
});
dispatch_async(requestQueue, ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(self.semaB);
});
});
dispatch_async(requestQueue, ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(self.semaC);
});
});
// 這裡注意一下!
// 如果不新建一個執行緒,而直接將上方的 `保證順序執行回撥` 程式碼放在這裡
// 序列佇列會將自己佇列中的任務放在主執行緒中執行,從而造成主執行緒阻塞(假死狀態)
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(taskQueue) object:nil];
[thread start];
}
- (void)taskQueue {
/ *
這裡註釋掉是因為有同學提示:在我們新建的執行緒中直接呼叫taskA/B/C,效果一樣,
想了一下,確實如此!作以修改
dispatch_queue_t taskQueue = dispatch_queue_create("com. taskQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(taskQueue, ^{
[self taskA];
});
dispatch_sync(taskQueue, ^{
[self taskB];
});
dispatch_sync(taskQueue, ^{
[self taskC];
});
*/
[self taskA];
[self taskB];
[self taskC];
}
複製程式碼
另附上- taskA/B/C
的程式碼,在等待訊號量後,列印很簡單的一段log日誌:
- (void)taskA {
// NSLog(@"%@", [NSThread currentThread]);
dispatch_semaphore_wait(self.semaA, DISPATCH_TIME_FOREVER);
NSLog(@"A Done");
}
- (void)taskB {
dispatch_semaphore_wait(self.semaB, DISPATCH_TIME_FOREVER);
NSLog(@"B Done");
}
- (void)taskC {
dispatch_semaphore_wait(self.semaC, DISPATCH_TIME_FOREVER);
NSLog(@"C Done");
}
複製程式碼
效果如下:
至此,就完成了設想場景的解決方案,有同學想到更好的解決方案,懇請不吝賜教!
最後想感謝一下 @詩雨 在群裡的點撥,URLSession的方案由於自己對這方面的知識不熟悉,日後學習完成再做補充哈哈哈。