實(shí)際工作中我們經(jīng)常會(huì)遇到有接口需要同時(shí)返回請(qǐng)求結(jié)果的情況奸晴,比如某一個(gè)詳情頁绽诚,可能有詳情信息和評(píng)論信息等多個(gè)接口需要請(qǐng)求魄藕,并且當(dāng)多個(gè)接口全部完成的時(shí)候内列,刷新當(dāng)前頁面的數(shù)據(jù),這里由于請(qǐng)求是異步的關(guān)系背率,我們不知道具體哪個(gè)請(qǐng)求會(huì)需要多久時(shí)間才能完成话瞧,所以今天分析一下解決方案嫩与。
在之前的《GCD的使用和原理》一文中有簡單講述這個(gè)問題,本文會(huì)針對(duì)這個(gè)問題詳細(xì)討論交排。
首先一個(gè)案例划滋,如果存在多個(gè)異步處理,如何能知道同時(shí)完成呢埃篓,這里能想到的是使用dispatch_group_async的方式將異步處理放入一個(gè)組中处坪,再使用dispatch_group_notify獲得所有組中異步完成的通知回調(diào)。
NSLog(@"全部開始-----%@", [NSThread currentThread]);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
sleep(4);
NSLog(@"子線程1-----%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"子線程2-----%@", [NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"全部結(jié)束-----%@", [NSThread currentThread]);
});
這里簡單模擬了異步的耗時(shí)操作都许,我們需要的結(jié)果是兩個(gè)子線程的任務(wù)全部完成之后稻薇,才回到主線程繼續(xù)任務(wù)嫂冻,下面看一下打印結(jié)果
全部開始-----<NSThread: 0x6040000741c0>{number = 1, name = main}
子線程2-----<NSThread: 0x604000277380>{number = 5, name = (null)}
子線程1-----<NSThread: 0x604000469080>{number = 4, name = (null)}
全部結(jié)束-----<NSThread: 0x6040000741c0>{number = 1, name = main}
這里可以很明顯的看出開始和結(jié)束都是主線程胶征,而結(jié)束之前的確執(zhí)行了兩個(gè)子線程的耗時(shí)任務(wù)
這里實(shí)現(xiàn)了我們的需求,當(dāng)然不只是一種方式可以實(shí)現(xiàn)桨仿,下面再嘗試一種方法看看睛低。
NSLog(@"全部開始-----%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
sleep(4);
NSLog(@"子線程1-----%@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
dispatch_async(queue, ^{
sleep(3);
NSLog(@"子線程2-----%@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"全部結(jié)束-----%@", [NSThread currentThread]);
這次沒有使用dispatch_group的方式,這個(gè)semaphore應(yīng)該也比較常用了(如果不常用的可以看我之前的關(guān)于GCD那篇文章)服傍,我們使用信號(hào)量的方式钱雷,使用wait阻止主線程后續(xù)任務(wù)的開展,當(dāng)信號(hào)量不為0的時(shí)候會(huì)-1并繼續(xù)執(zhí)行吹零,如果信號(hào)量為0罩抗,則等待,在得到信號(hào)增加之前就會(huì)持續(xù)等待灿椅,后面參數(shù)為時(shí)間類型表示永久套蒂,當(dāng)每個(gè)異步任務(wù)完成后,會(huì)給semaphore信號(hào)量+1茫蛹,當(dāng)所有任務(wù)完成之后操刀,主線程不再被阻止,會(huì)繼續(xù)任務(wù)婴洼。打印結(jié)果同上骨坑,這里不重復(fù)寫了,好奇的小伙伴們可以試下柬采。
以上使用了兩種方式解決異步任務(wù)的全部完成通知欢唾,下面該解決本文的需求了,就是當(dāng)多個(gè)請(qǐng)求全部完成的通知該怎么獲得粉捻。
有些朋友好奇多個(gè)請(qǐng)求和多個(gè)異步任務(wù)有什么區(qū)別匈辱,這里解釋下,多個(gè)異步任務(wù)我們可以指定這幾個(gè)異步任務(wù)為同一個(gè)組的任務(wù)杀迹,以及通過組的notify方法得到任務(wù)結(jié)束的通知亡脸,但是多個(gè)請(qǐng)求沒有這種條件押搪,所以不能完全用之前的方式處理。
先給一下錯(cuò)誤示范案例:
NSLog(@"即將開始多個(gè)請(qǐng)求");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
[[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
NSLog(@"請(qǐng)求1完成");
} failure:^(NSError *error) {
}];
});
dispatch_group_async(group, queue, ^{
[[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
NSLog(@"請(qǐng)求2完成");
} failure:^(NSError *error) {
}];
});
dispatch_group_notify(group, queue, ^{
NSLog(@"全部完成");
});
這里用了兩個(gè)請(qǐng)求任務(wù)放入異步操作中浅碾,下面看打印結(jié)果是否和我們預(yù)想的一樣
2018-09-11 22:43:04.469787+0800 Demo[2511:1215843] 即將開始多個(gè)請(qǐng)求
2018-09-11 22:43:04.472144+0800 Demo[2511:1216152] 全部完成
2018-09-11 22:43:05.598410+0800 Demo[2511:1215843] 請(qǐng)求1完成
2018-09-11 22:43:05.604425+0800 Demo[2511:1215843] 請(qǐng)求2完成
這里的時(shí)間很明顯告訴我們大州,同時(shí)返回的通知并沒有完成,原因是當(dāng)我們使用請(qǐng)求的時(shí)候垂谢,相當(dāng)于又一次開啟了一個(gè)異步操作厦画,請(qǐng)求的代碼會(huì)立即執(zhí)行完成,但是請(qǐng)求完成的異步回調(diào)并不能滥朱,所以我們要稍微改良一下我們的代碼
NSLog(@"即將開始多個(gè)請(qǐng)求");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
dispatch_group_enter(group);
[[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
dispatch_group_leave(group);
NSLog(@"請(qǐng)求1完成");
} failure:^(NSError *error) {
}];
});
dispatch_group_async(group, queue, ^{
dispatch_group_enter(group);
[[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
dispatch_group_leave(group);
NSLog(@"請(qǐng)求2完成");
} failure:^(NSError *error) {
}];
});
dispatch_group_notify(group, queue, ^{
NSLog(@"全部完成");
});
我們的改動(dòng)很小根暑,只有在請(qǐng)求之前加了enter函數(shù),以及請(qǐng)求成功之后加入leave函數(shù)徙邻,下面看一下成功的結(jié)果
2018-09-11 22:48:41.626897+0800 Gemii[2518:1218253] 即將開始多個(gè)請(qǐng)求
2018-09-11 22:48:45.881502+0800 Gemii[2518:1218253] 請(qǐng)求1完成
2018-09-11 22:48:48.982129+0800 Gemii[2518:1218253] 請(qǐng)求2完成
2018-09-11 22:48:48.984066+0800 Gemii[2518:1218425] 全部完成
很明顯我們的需求達(dá)到了排嫌,解決方案就是當(dāng)請(qǐng)求之前調(diào)用dispatch_group_enter()函數(shù),表明即將進(jìn)入另一個(gè)內(nèi)部操作中缰犁,后續(xù)任務(wù)暫時(shí)停止淳地,直到請(qǐng)求成功之后,調(diào)用dispatch_group_leave()函數(shù)帅容,表明另一個(gè)內(nèi)部操作完成颇象,可以進(jìn)行接下來的操作,于是兩個(gè)group_async同時(shí)完成并徘,調(diào)用notify通知
上面很清楚我們用dispatch_group_enter和dispatch_group_leave這一對(duì)函數(shù)實(shí)現(xiàn)了我們的需求遣钳,下面仍然用semaphore的方式處理這個(gè)問題,不過semaphore如果像之前一樣操作麦乞,同樣會(huì)出問題蕴茴,案例如下
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSLog(@"即將開始多個(gè)請(qǐng)求");
[[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
NSLog(@"請(qǐng)求1完成");
dispatch_semaphore_signal(semaphore);
} failure:^(NSError *error) {
}];
[[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
NSLog(@"請(qǐng)求2完成");
dispatch_semaphore_signal(semaphore);
} failure:^(NSError *error) {
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"全部完成");
這里的代碼也很清晰,和前文一樣路幸,我們認(rèn)為當(dāng)請(qǐng)求成功之后荐开,將信號(hào)量+1,直到兩個(gè)請(qǐng)求都完成解決wait的阻塞简肴,但是我們看一下結(jié)果
2018-09-11 23:12:55.898884+0800 Gemii[2600:1231519] 即將開始多個(gè)請(qǐng)求
這里只打印了開始的部分晃听,后續(xù)任務(wù)全部沒有執(zhí)行,并且頁面進(jìn)入了死鎖狀態(tài)砰识,造成這個(gè)的原因是什么呢能扒,看似和之前一樣,應(yīng)該沒有問題辫狼,但是這里注意初斑,我們主線程一開始就已經(jīng)進(jìn)入了wait狀態(tài),之前的方式是在異步的子線程中膨处,增加信號(hào)量见秤,但是我們這里想回到主線程的block已經(jīng)被wait擋住不能執(zhí)行砂竖,導(dǎo)致不能調(diào)用dispatch_semaphore_signal()函數(shù)增加信號(hào)量,所以造成了死鎖鹃答,那么解決辦法就是將請(qǐng)求放到異步隊(duì)列中
NSLog(@"即將開始多個(gè)請(qǐng)求");
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
[[URLBase sharedInstance] getBusinessDataWithSuccess:^(NSDictionary *result) {
NSLog(@"請(qǐng)求1完成---%@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
} failure:^(NSError *error) {
}];
});
dispatch_group_async(group, queue, ^{
[[URLBase sharedInstance] getHomeConfigurationWithSuccess:^(NSDictionary *result) {
NSLog(@"請(qǐng)求2完成---%@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
} failure:^(NSError *error) {
}];
});
dispatch_group_notify(group, queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"全部完成---%@", [NSThread currentThread]);
});
這里我們雖然使用了group的方式乎澄,但是沒有使用enter和leave兩個(gè)函數(shù),而是為了避免semaphore會(huì)在主線程死鎖而加入了異步操作测摔,下面是成功結(jié)果
2018-09-11 23:25:30.054214+0800 即將開始多個(gè)請(qǐng)求
2018-09-11 23:25:30.705859+0800 請(qǐng)求1完成---<NSThread: 0x1c02623c0>{number = 1, name = main}
2018-09-11 23:25:30.905989+0800 請(qǐng)求2完成---<NSThread: 0x1c02623c0>{number = 1, name = main}
2018-09-11 23:25:30.906441+0800 全部完成---<NSThread: 0x1c0870d40>{number = 11, name = (null)}
這樣看就很清晰置济,兩個(gè)請(qǐng)求完成是在主線程完成的回調(diào),但是wait以及完成后操作锋八,均在子線程中浙于,不會(huì)對(duì)主線程造成阻塞,所以可以實(shí)現(xiàn)本文需求
到這里我使用了兩種方式實(shí)現(xiàn)標(biāo)題的需求挟纱,有更多的解決辦法還期待大家的探索羞酗,這里就不多加贅述了,如果有更方便的方式樊销,歡迎在下方評(píng)論區(qū)進(jìn)行討論整慎,如果文章中有技術(shù)錯(cuò)誤還請(qǐng)指正脏款。
按照之前說的围苫,后續(xù)文章會(huì)討論多個(gè)請(qǐng)求同步執(zhí)行的方法,最近時(shí)間比較緊撤师,更新比較慢還請(qǐng)見諒剂府,如果有認(rèn)為本文有些作用的請(qǐng)點(diǎn)個(gè)贊支持下,感謝各方大佬~