iOS使用dispatch_group實現(xiàn)分組并發(fā)網(wǎng)絡請求

前言

在實際開發(fā)中我們通常會遇到這樣一種需求:某個頁面加載時通過網(wǎng)絡請求獲得相應的數(shù)據(jù),再做某些操作。有時候加載的內(nèi)容需要通過好幾個請求的數(shù)據(jù)組合而成,比如有兩個請求A和B,我們通常為了省事,會將B請求放在A請求成功的回調(diào)中發(fā)起,在B的成功回調(diào)中將數(shù)據(jù)組合起來,這樣做有明顯的問題:

  • 請求如果多了,需要寫許多嵌套的請求
  • 如果在除了最后一個請求前的某個請求失敗了,就不會執(zhí)行后面的請求,數(shù)據(jù)無法加載
  • 請求變成同步的,這是最大的問題,在網(wǎng)絡差的情況下,如果有n個請求,意味著用戶要等待n倍于并發(fā)請求的時間才能看到內(nèi)容

一、某界面存在多個請求,希望所有請求均結束才進行某操作。

同步請求這么low的方式當然是不可接受的,所以我們要并發(fā)這些請求,在所有請求都執(zhí)行完成功回調(diào)后,再做加載內(nèi)容或其他操作,考慮再三,選擇用GCD的dispatch_group。

dispatch_group通常有兩種用法,一種是

  • dispatch_group_async(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)

創(chuàng)建一個dispatch_group_t, 將并發(fā)的操作放在block中,在

dispatch_group_notify(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>)

的block中執(zhí)行多組block執(zhí)行完畢后的操作,對于網(wǎng)絡請求來說,在請求發(fā)出時他就算執(zhí)行完畢了,并不會等待回調(diào),所以不滿足我們的需求。

dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求1
        NSLog(@"Request_1");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求2
        NSLog(@"Request_2");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求3
        NSLog(@"Request_3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //界面刷新
        NSLog(@"任務均完成,刷新界面");
    });

打印如下

Request_2
Request_1
Request_3
任務均完成,刷新界面

網(wǎng)絡請求我們一般都用異步的,并不知道什么時候是否完成了。

  • 所以采用另一種用法:

使用dispatch_group_enter(group)和dispatch_group_leave(group),這種方式使用更為靈活,enter和leave必須配合使用,有幾次enter就要有幾次leave,否則group會一直存在。當所有enter的block都leave后,會執(zhí)行dispatch_group_notify的block。

我們當然可以在網(wǎng)絡請求前enter,在執(zhí)行完每個請求的成功回調(diào)后leave,再在notify中執(zhí)行內(nèi)容加載,這樣看來問題就解決了,就像這樣:

dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求1
        [網(wǎng)絡請求:{
        成功:dispatch_group_leave(group);
        失敗:dispatch_group_leave(group);
}];
    });
    dispatch_group_enter;
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求2
        [網(wǎng)絡請求:{
        成功:dispatch_group_leave;
        失?。篸ispatch_group_leave;
}];
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求3
        [網(wǎng)絡請求:{
        成功:dispatch_group_leave(group);
        失敗:dispatch_group_leave(group);
}];
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //界面刷新
        NSLog(@"任務均完成,刷新界面");
    });
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

打印如下

Request_2
Request_1
Request_3
任務均完成,刷新界面

二、某界面存在多個請求,希望請求依次執(zhí)行。

對于這個問題通常會通過線程依賴進行解決,因使用GCD設置線程依賴比較繁瑣,這里通過NSOperationQueue進行實現(xiàn),這里采用比較經(jīng)典的例子,三個任務分別為下載圖片,打水印和上傳圖片,三個任務需異步執(zhí)行但需要順序性。代碼如下,下載圖片、打水印、上傳圖片仍模擬為分別請求新聞列表3頁數(shù)據(jù)。

    //1.任務一:下載圖片
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_A];
    }];
 
    //2.任務二:打水印
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_B];
    }];
 
    //3.任務三:上傳圖片
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        [self request_C];
    }];
 
    //4.設置依賴
    [operation2 addDependency:operation1];      //任務二依賴任務一
    [operation3 addDependency:operation2];      //任務三依賴任務二
 
    //5.創(chuàng)建隊列并加入任務
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

首先我們使用未添加信號量dispatch_semaphore時運行,打印如下

B___圖片打水印
B___圖片打水印
B___圖片打水印
B___圖片打水印
B___圖片打水印
A___下載圖片
A___下載圖片
A___下載圖片
A___下載圖片
A___下載圖片
C___上傳打好水印的圖片
C___上傳打好水印的圖片
C___上傳打好水印的圖片
C___上傳打好水印的圖片
C___上傳打好水印的圖片

根據(jù)打印結果可見,若不對請求方法做處理,其運行結果并不是我們想要的,聯(lián)系實際需求,A、B、C請求分別對應下載圖片、打水印、上傳圖片,而此時運行順序則為B->A->C,在未獲得圖片時即執(zhí)行打水印操作明顯是錯誤的。重復運行亦會出現(xiàn)不同結果,即請求不做處理,其結果不可控無法預測。線程依賴設置并未起到作用。

解解決此問題的方法仍可通過信號量dispatch_semaphore進行解決。我們將請求方法替換為添加dispatch_semaphore限制的形式。
因此對于這種問題需要另辟蹊徑,這里我們就要借助GCD中的信號量dispatch_semaphore進行實現(xiàn),即營造線程同步情況。

dispatch_semaphore信號量為基于計數(shù)器的一種多線程同步機制。用于解決在多個線程訪問共有資源時候,會因為多線程的特性而引發(fā)數(shù)據(jù)出錯的問題。

如果semaphore計數(shù)大于等于1,計數(shù)-1,返回,程序繼續(xù)運行。如果計數(shù)為0,則等待。

dispatch_semaphore_signal(semaphore)為計數(shù)+1操作。dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)為設置等待時間,這里設置的等待時間是一直等待。我們可以通俗的理解為單柜臺排隊點餐,計數(shù)默認為0,每當有顧客點餐,計數(shù)+1,點餐結束-1歸零繼續(xù)等待下一位顧客。比較類似于NSLock。

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[網(wǎng)絡請求:{
        成功:dispatch_semaphore_signal(sema);
        失?。篸ispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

再次重復運行,我們會發(fā)現(xiàn)每次運行結果均一致,A、B、C三任務異步順序執(zhí)行(A->B->C)

A___下載圖片
A___下載圖片
A___下載圖片
A___下載圖片
A___下載圖片
B___圖片打水印
B___圖片打水印
B___圖片打水印
B___圖片打水印
B___圖片打水印
C___上傳打好水印的圖片
C___上傳打好水印的圖片
C___上傳打好水印的圖片
C___上傳打好水印的圖片
C___上傳打好水印的圖片

通過重復運行打印結果可證實確實實現(xiàn)了我們想要的效果。這樣即解決了所提出的問題二。

后續(xù)
如果看的不是特別明白,可以看看這篇文章,寫的很棒
點我進入

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 一. 重點: 1.dispatch_queue_create(生成Dispatch Queue) 2.Main D...
    BestJoker閱讀 1,685評論 2 2
  • 1 GCD 術語 1.1 Serial vs. Concurrent 串行 vs. 并發(fā) 概念:該術語描述執(zhí)行當前...
    NinthDay閱讀 4,134評論 2 38
  • Managing Units of Work(管理工作單位) 調(diào)度塊允許您直接配置隊列中各個工作單元的屬性。它們還...
    edison0428閱讀 8,234評論 0 1
  • 背景 擔心了兩周的我終于輪到去醫(yī)院做胃鏡檢查了!去的時候我都想好了最壞的可能(胃癌),之前在網(wǎng)上查的癥狀都很相似。...
    Dely閱讀 9,408評論 21 42
  • “你應該發(fā)現(xiàn)你現(xiàn)在生活中的一切美好、真實的東西?;厥走^去會使你產(chǎn)生競爭的意識,而年齡是無法競爭的......當我應...
    大大大栗子閱讀 1,223評論 0 1

友情鏈接更多精彩內(nèi)容