前言
在實際開發(fā)中我們通常會遇到這樣一種需求:某個頁面加載時通過網(wǎng)絡(luò)請求獲得相應(yīng)的數(shù)據(jù)疼鸟,再做某些操作爵政。有時候加載的內(nèi)容需要通過好幾個請求的數(shù)據(jù)組合而成误趴,比如有兩個請求A和B,我們通常為了省事路翻,會將B請求放在A請求成功的回調(diào)中發(fā)起,在B的成功回調(diào)中將數(shù)據(jù)組合起來茄靠,這樣做有明顯的問題:
- 請求如果多了茂契,需要寫許多嵌套的請求
- 如果在除了最后一個請求前的某個請求失敗了,就不會執(zhí)行后面的請求嘹黔,數(shù)據(jù)無法加載
- 請求變成同步的账嚎,這是最大的問題,在網(wǎng)絡(luò)差的情況下儡蔓,如果有n個請求郭蕉,意味著用戶要等待n倍于并發(fā)請求的時間才能看到內(nèi)容
一、某界面存在多個請求喂江,希望所有請求均結(jié)束才進(jìn)行某操作召锈。
同步請求這么low的方式當(dāng)然是不可接受的,所以我們要并發(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)絡(luò)請求來說,在請求發(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(@"任務(wù)均完成瘦麸,刷新界面");
});
打印如下
Request_2
Request_1
Request_3
任務(wù)均完成谁撼,刷新界面
網(wǎng)絡(luò)請求我們一般都用異步的,并不知道什么時候是否完成了滋饲。
- 所以采用另一種用法:
使用dispatch_group_enter(group)和dispatch_group_leave(group)厉碟,這種方式使用更為靈活,enter和leave必須配合使用了赌,有幾次enter就要有幾次leave墨榄,否則group會一直存在。當(dāng)所有enter的block都leave后勿她,會執(zhí)行dispatch_group_notify的block袄秩。
我們當(dāng)然可以在網(wǎng)絡(luò)請求前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)絡(luò)請求:{
成功: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)絡(luò)請求:{
成功:dispatch_group_leave;
失敱臣凇:dispatch_group_leave;
}];
});
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//請求3
[網(wǎng)絡(luò)請求:{
成功:dispatch_group_leave(group);
失敗:dispatch_group_leave(group);
}];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//界面刷新
NSLog(@"任務(wù)均完成玻蝌,刷新界面");
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
打印如下
Request_2
Request_1
Request_3
任務(wù)均完成蟹肘,刷新界面
二、某界面存在多個請求俯树,希望請求依次執(zhí)行帘腹。
對于這個問題通常會通過線程依賴進(jìn)行解決,因使用GCD設(shè)置線程依賴比較繁瑣许饿,這里通過NSOperationQueue進(jìn)行實現(xiàn)阳欲,這里采用比較經(jīng)典的例子,三個任務(wù)分別為下載圖片陋率,打水印和上傳圖片球化,三個任務(wù)需異步執(zhí)行但需要順序性。代碼如下瓦糟,下載圖片筒愚、打水印、上傳圖片仍模擬為分別請求新聞列表3頁數(shù)據(jù)菩浙。
//1.任務(wù)一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[self request_A];
}];
//2.任務(wù)二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[self request_B];
}];
//3.任務(wù)三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
[self request_C];
}];
//4.設(shè)置依賴
[operation2 addDependency:operation1]; //任務(wù)二依賴任務(wù)一
[operation3 addDependency:operation2]; //任務(wù)三依賴任務(wù)二
//5.創(chuàng)建隊列并加入任務(wù)
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ù)打印結(jié)果可見扯再,若不對請求方法做處理,其運行結(jié)果并不是我們想要的址遇,聯(lián)系實際需求,A斋竞、B倔约、C請求分別對應(yīng)下載圖片、打水印坝初、上傳圖片浸剩,而此時運行順序則為B->A->C,在未獲得圖片時即執(zhí)行打水印操作明顯是錯誤的鳄袍。重復(fù)運行亦會出現(xiàn)不同結(jié)果绢要,即請求不做處理,其結(jié)果不可控?zé)o法預(yù)測拗小。線程依賴設(shè)置并未起到作用重罪。
解解決此問題的方法仍可通過信號量dispatch_semaphore進(jìn)行解決。我們將請求方法替換為添加dispatch_semaphore限制的形式哀九。
因此對于這種問題需要另辟蹊徑剿配,這里我們就要借助GCD中的信號量dispatch_semaphore進(jìn)行實現(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è)置等待時間,這里設(shè)置的等待時間是一直等待挽荠。我們可以通俗的理解為單柜臺排隊點餐克胳,計數(shù)默認(rèn)為0,每當(dāng)有顧客點餐圈匆,計數(shù)+1漠另,點餐結(jié)束-1歸零繼續(xù)等待下一位顧客。比較類似于NSLock跃赚。
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[網(wǎng)絡(luò)請求:{
成功:dispatch_semaphore_signal(sema);
失敯蚀辍:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
再次重復(fù)運行性湿,我們會發(fā)現(xiàn)每次運行結(jié)果均一致,A满败、B肤频、C三任務(wù)異步順序執(zhí)行(A->B->C)
A___下載圖片
A___下載圖片
A___下載圖片
A___下載圖片
A___下載圖片
B___圖片打水印
B___圖片打水印
B___圖片打水印
B___圖片打水印
B___圖片打水印
C___上傳打好水印的圖片
C___上傳打好水印的圖片
C___上傳打好水印的圖片
C___上傳打好水印的圖片
C___上傳打好水印的圖片
通過重復(fù)運行打印結(jié)果可證實確實實現(xiàn)了我們想要的效果。這樣即解決了所提出的問題二算墨。
后續(xù)
如果看的不是特別明白宵荒,可以看看這篇文章,寫的很棒
點我進(jìn)入