前段時間學(xué)習(xí)了一下GCD相關(guān)的知識,看了不少同學(xué)寫的Blog,通過使用dispatch_group
和dispatch_semaphore
解決網(wǎng)絡(luò)請求同步、異步問題芜果,讓我受益良多,也激發(fā)了我的思考融师。
我提出了一種問題場景
多請求異步發(fā)起师幕,要求其回調(diào)按順序執(zhí)行。
題目很簡單诬滩,為了能方便大家的理解,我舉個栗子:A灭将、B疼鸟、C三個網(wǎng)絡(luò)請求,一同發(fā)出庙曙,當(dāng)然它們請求回來的時間肯定是不一致的空镜,但是我希望三個網(wǎng)絡(luò)請求對應(yīng)的blockA、blockB捌朴、blockC是按照順序執(zhí)行的吴攒。
也就是說就算網(wǎng)絡(luò)請求B回來了,但是A沒回來砂蔽,blockB也要等著A請求的blockA先執(zhí)行完才能執(zhí)行洼怔。
我想我的問題已經(jīng)描述的很清楚了,不知道讀到這里同學(xué)們有沒有想法左驾?
可以思考一下镣隶,當(dāng)然极谊!其實你使用各種標(biāo)識符控制、i++計量安岂、請求任務(wù)的Block中嵌套另一個請求任務(wù)的發(fā)起動作等……方法是可以解決這種場景問題轻猖。但是!既然是為了學(xué)習(xí)域那,就不要把為了規(guī)避問題當(dāng)做第一要務(wù)咙边。
接下來我說一下我的處理辦法,可能不是最好的辦法次员,也就是為大家提供一個思路败许。
1. 首先我們模擬一下網(wǎng)絡(luò)請求
此處模擬網(wǎng)絡(luò)求情寫的有點復(fù)雜了,之所以加到并行隊列中是想體現(xiàn)這三個網(wǎng)絡(luò)請求是異步并發(fā)的翠肘。
dispatch_queue_t requestQueue = dispatch_queue_create("com.requestQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(requestQueue, ^{
// 模擬第一個網(wǎng)絡(luò)請求requestA檐束,4s后返回
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 執(zhí)行requestA請求完成后的taskA
});
});
dispatch_async(requestQueue, ^{
// 模擬第一個網(wǎng)絡(luò)請求requestB,2s后返回
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 執(zhí)行requestB請求完成后的taskB
});
});
dispatch_async(requestQueue, ^{
// 模擬第一個網(wǎng)絡(luò)請求requestA束倍,3s后返回
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 執(zhí)行requestC請求完成后的taskC
});
});
2. 保證順序執(zhí)行回調(diào)
為了能順序執(zhí)行回調(diào)方法被丧,我想到了串行隊列,如果我把blockA/B/C
都丟到一個串行隊列中绪妹,那么就可以保證回調(diào)處理是順序執(zhí)行的了甥桂,所以有了下面的代碼:
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];
});
我將請求后需要執(zhí)行的task
預(yù)先加入到了一個串行隊列中。
3. 控制啟動的時機
任何一個task
的執(zhí)行都有兩個條件約束:
- 已執(zhí)行優(yōu)先級更高的
task
邮旷; -
task
本身對應(yīng)的request
已請求完成黄选;
既然串行隊列保證了第1點:我們的執(zhí)行順序,我們在加上第二點約束即可婶肩。
我選擇了使用信號量來通知請求是否完成办陷,并能夠使未完成的請求所對應(yīng)的task
保持等待狀態(tài)!簡直完美律歼。
創(chuàng)建了3個信號量的實例
初始化信號量為0民镜。
@property (nonatomic, strong) dispatch_semaphore_t semaA;
@property (nonatomic, strong) dispatch_semaphore_t semaB;
@property (nonatomic, strong) dispatch_semaphore_t semaC;
修改上面虛擬網(wǎng)絡(luò)請求的代碼
- (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(), ^{
// 通過信號量通知
// 執(zhí)行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);
});
});
// 這里注意一下!
// 如果不新建一個線程,而直接將上方的 `保證順序執(zhí)行回調(diào)` 代碼放在這里
// 串行隊列會將自己隊列中的任務(wù)放在主線程中執(zhí)行险毁,從而造成主線程阻塞(假死狀態(tài))
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(taskQueue) object:nil];
[thread start];
}
- (void)taskQueue {
/ *
這里注釋掉是因為有同學(xué)提示:在我們新建的線程中直接調(diào)用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");
}
效果如下:至此馋嗜,就完成了設(shè)想場景的解決方案,有同學(xué)想到更好的解決方案吵瞻,懇請不吝賜教嵌戈!
最后想感謝一下 @詩雨 在群里的點撥覆积,URLSession的方案由于自己對這方面的知識不熟悉,日后學(xué)習(xí)完成再做補充哈哈哈熟呛。