前言
在實際開發(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ù)
如果看的不是特別明白,可以看看這篇文章,寫的很棒
點我進入