多個(gè)網(wǎng)絡(luò)請(qǐng)求成功返回再執(zhí)行另外任務(wù)的思路分析(iOS)

前言

今天我們來(lái)討論一個(gè)經(jīng)常出現(xiàn)的需求場(chǎng)景酪捡,也是一個(gè)老話題。在開(kāi)發(fā)中我們往往會(huì)遇到需要進(jìn)行多個(gè)網(wǎng)絡(luò)請(qǐng)求,并且需要多個(gè)網(wǎng)絡(luò)請(qǐng)求成功返回后再做其他事的場(chǎng)景陈症。比如同一個(gè)界面顯示的內(nèi)容需要用到兩個(gè)網(wǎng)絡(luò)接口,而需求又希望成功返回兩個(gè)接口的數(shù)據(jù)再進(jìn)行頁(yè)面展示震糖;又比如喜歡挖坑的后臺(tái)同學(xué)就只提供了返回一條數(shù)據(jù)的接口录肯,但需求卻希望我們?cè)谝粋€(gè)界面同時(shí)顯示幾條數(shù)據(jù)的情況。

正題

我們不討論什么執(zhí)行完一個(gè)請(qǐng)求再執(zhí)行一個(gè)這種串行的低效率方法吊说,以下分析都是在異步的基礎(chǔ)上進(jìn)行的论咏。
廢話少說(shuō)优炬,直奔正題!先上個(gè)網(wǎng)絡(luò)請(qǐng)求的模擬代碼厅贪。

//模擬一個(gè)網(wǎng)絡(luò)請(qǐng)求方法 get/post/put...etc
- (void)httpRequest:(NSString *)method param:(NSDictionary *)param completion:(void(^)(id response))block{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSString *commend = [param objectForKey:commandKey];
        NSLog(@"request:%@ run in thread:%@", commend, [NSThread currentThread]);
        NSTimeInterval sleepInterval = arc4random() % 10;
        [NSThread sleepForTimeInterval:sleepInterval];
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"requset:%@ done!", commend);
            block(nil);
        });
    });
}

不可行的直接使用group的方案

對(duì)于這樣的需求蠢护,我們自然而然就想到了使用GCD group,先上代碼

- (void)testUsingGroup1{
    NSArray *commandArray = @[@"requestcommand1", @"requestcommand2", @"requestcommand3", @"requestcommand4", @"requestcommand5"];
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    [commandArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        dispatch_group_async(group, queue, ^{
            NSLog(@"%@ in group thread:%@", obj, [NSThread currentThread]);
            [self httpRequest:nil param:@{commandKey : obj} completion:^(id response) {
                
            }];
        });
    }];
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"all http request done!");
        NSLog(@"UI update in main thread!");
    });
}

代碼很快寫(xiě)完了养涮,但卻存在問(wèn)題葵硕,我們來(lái)看一下運(yùn)行結(jié)果:

2017-04-29 21:49:27.336 TestMutiRequest[1345:82575] requestcommand2 in group thread:<NSThread: 0x600000262a40>{number = 4, name = (null)}
2017-04-29 21:49:27.336 TestMutiRequest[1345:82576] requestcommand1 in group thread:<NSThread: 0x60000007f2c0>{number = 3, name = (null)}
2017-04-29 21:49:27.336 TestMutiRequest[1345:82578] requestcommand3 in group thread:<NSThread: 0x608000263c00>{number = 5, name = (null)}
2017-04-29 21:49:27.336 TestMutiRequest[1345:82638] requestcommand4 in group thread:<NSThread: 0x600000262b00>{number = 6, name = (null)}
2017-04-29 21:49:27.336 TestMutiRequest[1345:82575] requestcommand5 in group thread:<NSThread: 0x600000262a40>{number = 4, name = (null)}
2017-04-29 21:49:27.336 TestMutiRequest[1345:82576] request:requestcommand2 run in thread:<NSThread: 0x60000007f2c0>{number = 3, name = (null)}
2017-04-29 21:49:27.337 TestMutiRequest[1345:82639] request:requestcommand1 run in thread:<NSThread: 0x608000264000>{number = 7, name = (null)}
2017-04-29 21:49:27.337 TestMutiRequest[1345:82578] request:requestcommand3 run in thread:<NSThread: 0x608000263c00>{number = 5, name = (null)}
2017-04-29 21:49:27.337 TestMutiRequest[1345:82638] request:requestcommand4 run in thread:<NSThread: 0x600000262b00>{number = 6, name = (null)}
2017-04-29 21:49:27.338 TestMutiRequest[1345:82575] request:requestcommand5 run in thread:<NSThread: 0x600000262a40>{number = 4, name = (null)}
2017-04-29 21:49:27.391 TestMutiRequest[1345:82507] all http request done!
2017-04-29 21:49:27.429 TestMutiRequest[1345:82507] UI update in main thread!
2017-04-29 21:49:27.432 TestMutiRequest[1345:82507] requset:requestcommand2 done!
2017-04-29 21:49:27.435 TestMutiRequest[1345:82507] requset:requestcommand3 done!
2017-04-29 21:49:27.437 TestMutiRequest[1345:82507] requset:requestcommand4 done!
2017-04-29 21:49:28.347 TestMutiRequest[1345:82507] requset:requestcommand5 done!
2017-04-29 21:49:35.399 TestMutiRequest[1345:82507] requset:requestcommand1 done!

注意這里:

2017-04-29 21:49:27.338 TestMutiRequest[1345:82575] request:requestcommand5 run in thread:<NSThread: 0x600000262a40>{number = 4, name = (null)}
2017-04-29 21:49:27.391 TestMutiRequest[1345:82507] all http request done!
2017-04-29 21:49:27.429 TestMutiRequest[1345:82507] UI update in main thread!
2017-04-29 21:49:27.432 TestMutiRequest[1345:82507] requset:requestcommand2 done!

結(jié)果很明顯,并不能得出我們需要的結(jié)果9嵯拧贬芥!
問(wèn)題究竟出現(xiàn)哪呢?P觥蘸劈!
讓我們?cè)诨仡櫼幌耮roup的概念,group的設(shè)計(jì)就是為了方便我們執(zhí)行完一系列的任務(wù)之后再執(zhí)行其他的任務(wù)尊沸,但是不能忽視的是威沫,這里的任務(wù)是有要求的,這里的任務(wù)必須要是同步執(zhí)行的M葑ā棒掠!如果任務(wù)是異步的,group只會(huì)執(zhí)行完任務(wù)里面異步之前的代碼以及分發(fā)異步任務(wù)就返回了Fㄉ獭烟很!也就代表分發(fā)group的當(dāng)前這個(gè)任務(wù)完成了!但事實(shí)卻是這個(gè)任務(wù)的一部分子任務(wù)在其他線程執(zhí)行了蜡镶,而且不一定已執(zhí)行結(jié)束返回雾袱。
問(wèn)題分析到這里,理所當(dāng)然的就會(huì)出現(xiàn)以上結(jié)果的問(wèn)題官还。
解決的方案也很自然想法了芹橡,就是想辦法使分發(fā)到group的任務(wù)是同步執(zhí)行的。
順便提一點(diǎn)望伦,雖然任務(wù)未能順利完成林说,但我們可以注意到GCD實(shí)現(xiàn)的一些細(xì)節(jié),在這里group到另外異步方法的執(zhí)行屯伞,GCD并沒(méi)有重新創(chuàng)建新的線程腿箩,而是重用了group已創(chuàng)建的線程。

改進(jìn)的group方案

這里我們使用dispatch_semaphore_t使單個(gè)請(qǐng)求任務(wù)同步執(zhí)行劣摇。

- (void)httpRequest2:(NSString *)method param:(NSDictionary *)param completion:(void(^)(id response))block{
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    [self httpRequest:method param:param completion:^(id response) {
        if (block) {
            block(response);
        }
        dispatch_semaphore_signal(sem);
    }];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

testUsingGroup方法也相應(yīng)改成對(duì)httpRequest2方法的調(diào)用

- (void)testUsingGroup2{
    NSArray *commandArray = @[@"requestcommand1", @"requestcommand2", @"requestcommand3", @"requestcommand4", @"requestcommand5"];
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    [commandArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        dispatch_group_async(group, queue, ^{
            NSLog(@"%@ in group thread:%@", obj, [NSThread currentThread]);
            [self httpRequest2:nil param:@{commandKey : obj} completion:^(id response) {
                
            }];
        });
    }];
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"all http request done!");
        NSLog(@"UI update in main thread!");
    });  
}

我們?cè)賮?lái)看一下結(jié)果:

2017-04-29 22:02:01.744 TestMutiRequest[1381:90462] requestcommand4 in group thread:<NSThread: 0x60000007bfc0>{number = 6, name = (null)}
2017-04-29 22:02:01.744 TestMutiRequest[1381:90404] requestcommand2 in group thread:<NSThread: 0x608000073880>{number = 4, name = (null)}
2017-04-29 22:02:01.744 TestMutiRequest[1381:90406] requestcommand1 in group thread:<NSThread: 0x60000007ba80>{number = 3, name = (null)}
2017-04-29 22:02:01.744 TestMutiRequest[1381:90403] requestcommand3 in group thread:<NSThread: 0x60000007bec0>{number = 5, name = (null)}
2017-04-29 22:02:01.745 TestMutiRequest[1381:90463] requestcommand5 in group thread:<NSThread: 0x60000007be80>{number = 7, name = (null)}
2017-04-29 22:02:01.745 TestMutiRequest[1381:90464] request:requestcommand4 run in thread:<NSThread: 0x60000007c440>{number = 8, name = (null)}
2017-04-29 22:02:01.746 TestMutiRequest[1381:90465] request:requestcommand2 run in thread:<NSThread: 0x60000007c4c0>{number = 9, name = (null)}
2017-04-29 22:02:01.746 TestMutiRequest[1381:90466] request:requestcommand1 run in thread:<NSThread: 0x60000007c540>{number = 10, name = (null)}
2017-04-29 22:02:01.746 TestMutiRequest[1381:90464] request:requestcommand3 run in thread:<NSThread: 0x60000007c440>{number = 8, name = (null)}
2017-04-29 22:02:01.746 TestMutiRequest[1381:90467] request:requestcommand5 run in thread:<NSThread: 0x608000073ec0>{number = 11, name = (null)}
2017-04-29 22:02:01.751 TestMutiRequest[1381:90356] requset:requestcommand4 done!
2017-04-29 22:02:01.821 TestMutiRequest[1381:90356] requset:requestcommand3 done!
2017-04-29 22:02:02.817 TestMutiRequest[1381:90356] requset:requestcommand1 done!
2017-04-29 22:02:03.796 TestMutiRequest[1381:90356] requset:requestcommand5 done!
2017-04-29 22:02:07.817 TestMutiRequest[1381:90356] requset:requestcommand2 done!
2017-04-29 22:02:07.818 TestMutiRequest[1381:90356] all http request done!
2017-04-29 22:02:07.818 TestMutiRequest[1381:90356] UI update in main thread!

這個(gè)結(jié)果就是我們預(yù)期希望得到的珠移!
但是不能高興的太早,這個(gè)方法需要實(shí)現(xiàn)了我們的需求,但是確實(shí)存在問(wèn)題的剑梳。我們可以看一下當(dāng)前的線程情況唆貌。整整比上一種不可行方案多出了一倍的線程數(shù)(5條線程->10條線程);恕垢乙!
這是怎么發(fā)生的呢?剛好是因?yàn)槲覀儼颜?qǐng)求方法改成了同步的语卤,但是網(wǎng)絡(luò)請(qǐng)求是在其他線程執(zhí)行的追逮!系統(tǒng)分配給執(zhí)行httpRequest2的線程必須等待進(jìn)行具體網(wǎng)絡(luò)請(qǐng)求的線程執(zhí)行結(jié)束后再能繼續(xù)執(zhí)行,否則繼續(xù)等待粹舵,相對(duì)上一種方案來(lái)說(shuō)钮孵,這個(gè)線程就是剛好多出來(lái)的線程,這在上一種方案是不存在的眼滤。也就是剛好多了一倍巴席。雖然有代價(jià),但是我們的任務(wù)也算順利完成了诅需。

不額外增加多一倍線程的方法(dispatch_semaphore_t)

使用信號(hào)量

- (void)testUsingSemaphore{
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
    NSArray *commandArray = @[@"requestcommand1", @"requestcommand2", @"requestcommand3", @"requestcommand4", @"requestcommand5"];
    
    NSInteger commandCount = [commandArray count];
    //代表http訪問(wèn)返回的數(shù)量
    //這里模仿的http請(qǐng)求block塊都是在同一線程(主線程)執(zhí)行返回的漾唉,所以對(duì)這個(gè)變量的訪問(wèn)不存在資源競(jìng)爭(zhēng)問(wèn)題,故不需要枷鎖處理
    //如果網(wǎng)絡(luò)請(qǐng)求在不同線程返回堰塌,要對(duì)這個(gè)變量進(jìn)行枷鎖處理赵刑,不然很會(huì)有資源競(jìng)爭(zhēng)危險(xiǎn)
    __block NSInteger httpFinishCount = 0;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //demo testUsingSemaphore方法是在主線程調(diào)用的,不直接調(diào)用遍歷執(zhí)行场刑,而是嵌套了一個(gè)異步般此,是為了避免主線程阻塞
        NSLog(@"start all http dispatch in thread: %@", [NSThread currentThread]);
        [commandArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [self httpRequest:nil param:@{commandKey : obj} completion:^(id response) {
                //全部請(qǐng)求返回才觸發(fā)signal
                if (++httpFinishCount == commandCount) {
                    dispatch_semaphore_signal(sem);
                }
            }];
        }];
        //如果全部請(qǐng)求沒(méi)有返回則該線程會(huì)一直阻塞
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"all http request done! end thread: %@", [NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"UI update in main thread!");
        });
    });
    
}

這是種可行的方法,思路很簡(jiǎn)單:任務(wù)分發(fā)線程進(jìn)行等待牵现,所有網(wǎng)絡(luò)請(qǐng)求成功返回后才發(fā)送信號(hào)量铐懊,任務(wù)分發(fā)線程繼續(xù)執(zhí)行。直接上結(jié)果:

2017-04-29 22:25:45.498 TestMutiRequest[1469:105980] start all http dispatch in thread: <NSThread: 0x608000260680>{number = 3, name = (null)}
2017-04-29 22:25:45.499 TestMutiRequest[1469:106008] request:requestcommand3 run in thread:<NSThread: 0x60000007ec80>{number = 6, name = (null)}
2017-04-29 22:25:45.499 TestMutiRequest[1469:105983] request:requestcommand2 run in thread:<NSThread: 0x608000260c00>{number = 5, name = (null)}
2017-04-29 22:25:45.499 TestMutiRequest[1469:105981] request:requestcommand1 run in thread:<NSThread: 0x60000007e000>{number = 4, name = (null)}
2017-04-29 22:25:45.499 TestMutiRequest[1469:106009] request:requestcommand4 run in thread:<NSThread: 0x608000260d40>{number = 7, name = (null)}
2017-04-29 22:25:45.499 TestMutiRequest[1469:106010] request:requestcommand5 run in thread:<NSThread: 0x608000260b80>{number = 8, name = (null)}
2017-04-29 22:25:45.519 TestMutiRequest[1469:105944] requset:requestcommand1 done!
2017-04-29 22:25:47.500 TestMutiRequest[1469:105944] requset:requestcommand4 done!
2017-04-29 22:25:49.559 TestMutiRequest[1469:105944] requset:requestcommand3 done!
2017-04-29 22:25:50.558 TestMutiRequest[1469:105944] requset:requestcommand5 done!
2017-04-29 22:25:52.571 TestMutiRequest[1469:105944] requset:requestcommand2 done!
2017-04-29 22:25:52.572 TestMutiRequest[1469:105980] all http request done! end thread: <NSThread: 0x608000260680>{number = 3, name = (null)}
2017-04-29 22:25:52.572 TestMutiRequest[1469:105944] UI update in main thread!

結(jié)論:
從效率和資源使用的角度來(lái)看瞎疼,最后一種只使用信號(hào)量的方法是最好的居扒,但是卻有點(diǎn)使用一個(gè)外部變量來(lái)保存執(zhí)行次數(shù)的嫌疑。
另外本文的思路不僅適用于網(wǎng)絡(luò)請(qǐng)求這種場(chǎng)景丑慎,其他需要異步執(zhí)行的類(lèi)似場(chǎng)景也是適用的喜喂,比如數(shù)據(jù)庫(kù)操作等。
個(gè)人水平有限竿裂,如果你有更好的方案或者覺(jué)得不對(duì)的地方玉吁,隨時(shí)歡迎在評(píng)論留言交流學(xué)習(xí)!腻异!

最后附上demo地址:
https://github.com/Calvix-Xu/TestMultiRequest.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末进副,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌影斑,老刑警劉巖给赞,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異矫户,居然都是意外死亡片迅,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)皆辽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)柑蛇,“玉大人,你說(shuō)我怎么就攤上這事驱闷〕芴ǎ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵空另,是天一觀的道長(zhǎng)盆耽。 經(jīng)常有香客問(wèn)我,道長(zhǎng)扼菠,這世上最難降的妖魔是什么摄杂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮娇豫,結(jié)果婚禮上匙姜,老公的妹妹穿的比我還像新娘。我一直安慰自己冯痢,他們只是感情好氮昧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著浦楣,像睡著了一般袖肥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上振劳,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天椎组,我揣著相機(jī)與錄音,去河邊找鬼历恐。 笑死寸癌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弱贼。 我是一名探鬼主播蒸苇,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吮旅!你這毒婦竟也來(lái)了溪烤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎檬嘀,沒(méi)想到半個(gè)月后槽驶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸳兽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年掂铐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贸铜。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡堡纬,死狀恐怖聂受,靈堂內(nèi)的尸體忽然破棺而出蒿秦,到底是詐尸還是另有隱情,我是刑警寧澤蛋济,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布棍鳖,位于F島的核電站,受9級(jí)特大地震影響碗旅,放射性物質(zhì)發(fā)生泄漏渡处。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一祟辟、第九天 我趴在偏房一處隱蔽的房頂上張望医瘫。 院中可真熱鬧,春花似錦旧困、人聲如沸醇份。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)僚纷。三九已至,卻和暖如春拗盒,著一層夾襖步出監(jiān)牢的瞬間怖竭,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工陡蝇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痊臭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓登夫,卻偏偏與公主長(zhǎng)得像广匙,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子悼嫉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容