1. 程式人生 > >iOS使用dispatch_group實現分組併發網路請求

iOS使用dispatch_group實現分組併發網路請求

前言

在實際開發中我們通常會遇到這樣一種需求:某個頁面載入時通過網路請求獲得相應的資料,再做某些操作。有時候載入的內容需要通過好幾個請求的資料組合而成,比如有兩個請求A和B,我們通常為了省事,會將B請求放在A請求成功的回撥中發起,在B的成功回撥中將資料組合起來,這樣做有明顯的問題:

  • 請求如果多了,需要寫許多巢狀的請求
  • 如果在除了最後一個請求前的某個請求失敗了,就不會執行後面的請求,資料無法載入
  • 請求變成同步的,這是最大的問題,在網路差的情況下,如果有n個請求,意味著使用者要等待n倍於併發請求的時間才能看到內容

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

同步請求這麼low的方式當然是不可接受的,所以我們要併發這些請求,在所有請求都執行完成功回撥後,再做載入內容或其他操作,考慮再三,選擇用GCD的dispatch_group。

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

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

建立一個dispatch_group_t, 將併發的操作放在block中,在

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

的block中執行多組block執行完畢後的操作,對於網路請求來說,在請求發出時他就算執行完畢了,並不會等待回撥,所以不滿足我們的需求。

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
任務均完成,重新整理介面

網路請求我們一般都用非同步的,並不知道什麼時候是否完成了。

  • 所以採用另一種用法:

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

我們當然可以在網路請求前enter,在執行完每個請求的成功回撥後leave,再在notify中執行內容載入,這樣看來問題就解決了,就像這樣:

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
        [網路請求:{
        成功: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
        [網路請求:{
        成功:dispatch_group_leave;
        失敗:dispatch_group_leave;
}];
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //請求3
        [網路請求:{
        成功: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
任務均完成,重新整理介面

二、某介面存在多個請求,希望請求依次執行。

對於這個問題通常會通過執行緒依賴進行解決,因使用GCD設定執行緒依賴比較繁瑣,這裡通過NSOperationQueue進行實現,這裡採用比較經典的例子,三個任務分別為下載圖片,打水印和上傳圖片,三個任務需非同步執行但需要順序性。程式碼如下,下載圖片、打水印、上傳圖片仍模擬為分別請求新聞列表3頁資料。

    //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.建立佇列並加入任務
    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___上傳打好水印的圖片

根據列印結果可見,若不對請求方法做處理,其執行結果並不是我們想要的,聯絡實際需求,A、B、C請求分別對應下載圖片、打水印、上傳圖片,而此時執行順序則為B->A->C,在未獲得圖片時即執行打水印操作明顯是錯誤的。重複執行亦會出現不同結果,即請求不做處理,其結果不可控無法預測。執行緒依賴設定並未起到作用。

解解決此問題的方法仍可通過訊號量dispatch_semaphore進行解決。我們將請求方法替換為新增dispatch_semaphore限制的形式。
因此對於這種問題需要另闢蹊徑,這裡我們就要藉助GCD中的訊號量dispatch_semaphore進行實現,即營造執行緒同步情況。

dispatch_semaphore訊號量為基於計數器的一種多執行緒同步機制。用於解決在多個執行緒訪問共有資源時候,會因為多執行緒的特性而引發資料出錯的問題。

如果semaphore計數大於等於1,計數-1,返回,程式繼續執行。如果計數為0,則等待。

dispatch_semaphore_signal(semaphore)為計數+1操作。dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)為設定等待時間,這裡設定的等待時間是一直等待。我們可以通俗的理解為單櫃檯排隊點餐,計數預設為0,每當有顧客點餐,計數+1,點餐結束-1歸零繼續等待下一位顧客。比較類似於NSLock。

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[網路請求:{
        成功:dispatch_semaphore_signal(sema);
        失敗:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

再次重複執行,我們會發現每次執行結果均一致,A、B、C三任務非同步順序執行(A->B->C)

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

通過重複執行列印結果可證實確實實現了我們想要的效果。這樣即解決了所提出的問題二。

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

 



作者:so_what
連結:https://www.jianshu.com/p/657e994aeee2
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。