整理自raywenderlich沪伙。
1.GCD是嘛喷好?
GCD是Grand Central Dispatch的縮寫泌类,是蘋果對(duì)多核硬件上執(zhí)行并發(fā)代碼的一種支持。
它有以下優(yōu)點(diǎn):
- GCD通過把計(jì)算密集型任務(wù)放于后臺(tái)運(yùn)行藤滥,以此提高APP的響應(yīng)速度鳖粟。
- GCD提供了更簡(jiǎn)單的并發(fā)模型,它優(yōu)于線程鎖拙绊,并且?guī)椭惚苊獠l(fā)bug向图。
- GCD基于底層、高性能的優(yōu)化常規(guī)類型的代碼标沪,例如單例榄攀。
2.GCD相關(guān)術(shù)語(yǔ)
串行和并發(fā)(Serial vs. Concurrent)
串行和并發(fā)描述了任務(wù)之間執(zhí)行的時(shí)機(jī)。任務(wù)如果是串行的金句,那么在同一時(shí)間只執(zhí)行一個(gè)任務(wù)檩赢。并發(fā)的多個(gè)任務(wù)被執(zhí)行的時(shí)候,可能是在同一時(shí)間违寞。(覺得有點(diǎn)繞漠畜?沒關(guān)系,后面有美圖-_-`)坞靶。
同步和異步(Synchronous vs. Asynchronous)
同步和異步描述了一個(gè)函數(shù)相對(duì)于另一個(gè)函數(shù)何時(shí)執(zhí)行完畢。
同步的函數(shù)只有當(dāng)它調(diào)用的任務(wù)執(zhí)行完蝴悉,才會(huì)返回彰阴。
而異步函數(shù),會(huì)立即返回拍冠。雖然它也命令任務(wù)執(zhí)行完尿这,但它并不等待任務(wù)執(zhí)行完。如此庆杜,異步函數(shù)就不會(huì)阻塞當(dāng)前線程射众。
危險(xiǎn)區(qū)(Critical Section)
它是指一段代碼一定不能被并發(fā)執(zhí)行,也就是說晃财,它不能同時(shí)在兩個(gè)線程里叨橱。這是因?yàn)榇a在并發(fā)操作共享資源(例如,NSMutableArray)時(shí),該資源可能會(huì)損壞罗洗。
競(jìng)態(tài)條件(Race Condition)
它指這樣一種情形:在軟件系統(tǒng)中愉舔,指定序列的執(zhí)行行為或者事件的執(zhí)行時(shí)間,沒有被有效規(guī)范(例如伙菜,并發(fā)任務(wù)中的執(zhí)行命令)轩缤。競(jìng)態(tài)條件會(huì)產(chǎn)生一些不可預(yù)測(cè)的行為,這些行為通過代碼檢查是很難發(fā)現(xiàn)的贩绕。
死鎖(Deadlock)
兩個(gè)或多個(gè)線程滿足以下情況火的,就被稱為死鎖:它們陷入了相互等待對(duì)方完成或執(zhí)行一個(gè)動(dòng)作。第一個(gè)無法完成是因?yàn)樗诘却诙€(gè)完成淑倾。而第二個(gè)無法完成是因?yàn)樗诘却谝粋€(gè)完成馏鹤。
線程安全(Thread Safe)
線程安全的代碼可以被多線程或者說并發(fā)任務(wù)安全調(diào)用,而不引起任何問題(數(shù)據(jù)損壞踊淳、崩潰等等)假瞬。線程不安全的代碼在同一時(shí)間,只能被同一環(huán)境調(diào)用迂尝。一個(gè)線程安全的例子就是NSDictionary
脱茉。你可以在多線程中調(diào)用,沒有任何問題垄开。相對(duì)來說琴许,NSMutableDictionary
就是線程不安全的,它在同一時(shí)間只能被一個(gè)線程調(diào)用溉躲。
環(huán)境切換(Context Switch)
當(dāng)你在不同的線程中切換執(zhí)行的時(shí)候榜田,對(duì)執(zhí)行狀態(tài)的儲(chǔ)存和恢復(fù)的處理,稱為環(huán)境切換锻梳。當(dāng)你寫多任務(wù)APP時(shí)箭券,這種處理相當(dāng)普遍,但是要付出一些額外的成本疑枯。
3.并發(fā)執(zhí)行和平行執(zhí)行(Concurrency vs Parallelism)
并發(fā)執(zhí)行和平行執(zhí)行經(jīng)常相提并論辩块,所以值得我們簡(jiǎn)單的區(qū)分一下。
并發(fā)執(zhí)行的每一部分是被"同時(shí)執(zhí)行"的荆永。事實(shí)上废亭,這種"同時(shí)"取決于并發(fā)執(zhí)行時(shí)的系統(tǒng)。
多核設(shè)備
執(zhí)行多線程利用的是平行執(zhí)行具钥,但是豆村,為了單核設(shè)備
也能實(shí)現(xiàn)并發(fā),它需要執(zhí)行一會(huì)兒一個(gè)線程(A線程)骂删,然后環(huán)境切換掌动,再去執(zhí)行另一個(gè)線程或進(jìn)程(B線程)四啰,然后再回來執(zhí)行前一個(gè)線程(A線程)。它切換的很快坏匪,以至于給了你平行執(zhí)行的錯(cuò)覺拟逮,就像下圖展示的:
盡管你在GCD下,寫了并發(fā)執(zhí)行的代碼适滓,但是敦迄,是由GCD來決定平行執(zhí)行是否是必要的。平行執(zhí)行要求并發(fā)執(zhí)行凭迹,但是并發(fā)執(zhí)行并不能保證是平行執(zhí)行罚屋。
4.隊(duì)列(Queues)
GCD提供dispatch queues
來操作代碼塊。這些隊(duì)列管理你提交給GCD的任務(wù)嗅绸,并且按照FIFO(first input first output ,先入先出)順序執(zhí)行脾猛。這保證了隊(duì)列中的第一個(gè)任務(wù)第一個(gè)被執(zhí)行,第二個(gè)任務(wù)第二個(gè)被執(zhí)行鱼鸠,以此類推猛拴。
所有的dispatch queues 自身都是線程安全的,所以你可以在多線程中使用它們蚀狰。當(dāng)你理解了dispatch queues為你提供了線程安全的代碼后愉昆,你就能理解GCD的偉大了。理解的關(guān)鍵在于你要選對(duì)dispatch queue麻蹋,并且提交給queue合適的函數(shù)跛溉。
串行隊(duì)列(Serial Queues)
串行隊(duì)列同一時(shí)間只執(zhí)行一個(gè)任務(wù),只有當(dāng)前一個(gè)任務(wù)執(zhí)行完扮授,下一個(gè)任務(wù)才開始芳室。不過,我們無法知道一個(gè)任務(wù)結(jié)束刹勃,到下一個(gè)任務(wù)開始之間所需的時(shí)間堪侯,就像下圖所示:
這些任務(wù)開始執(zhí)行的時(shí)間是由GCD控制的,你只能肯定GCD在同一時(shí)間只執(zhí)行一個(gè)任務(wù)荔仁,而且執(zhí)行順序是當(dāng)初它們被加到隊(duì)列中的順序伍宦。
因?yàn)樵诖嘘?duì)列中,不可能并發(fā)的處理兩個(gè)任務(wù)咕晋,所以沒有進(jìn)入到危險(xiǎn)區(qū)(critical section)的風(fēng)險(xiǎn),從而保證危險(xiǎn)區(qū)不可能進(jìn)入競(jìng)態(tài)條件收奔。
并發(fā)隊(duì)列(Concurrent Queues)
并發(fā)隊(duì)列保證任務(wù)是按照加入時(shí)的順序開始的掌呜,僅此而已,你再不能從并發(fā)隊(duì)列中得到其他保證了坪哄。各個(gè)任務(wù)執(zhí)行完的順序是不可知的质蕉,下一個(gè)代碼塊何時(shí)開始執(zhí)行是不可知的势篡,特定時(shí)間里,有多少個(gè)代碼塊在運(yùn)行也是不可知的模暗。這些還是由GCD控制的禁悠。
下圖展示了在GCD下,四個(gè)并發(fā)任務(wù)執(zhí)行的例子:
從圖中發(fā)現(xiàn)兑宇,代碼塊1碍侦,2,3幾乎同時(shí)開始系冗,但依然是一個(gè)跟著一個(gè)械荷。代碼塊0開始了一段時(shí)間后摆出,代碼塊1才開始。代碼塊3在代碼塊2之后開始濒旦,卻先于代碼塊2結(jié)束。
一個(gè)代碼塊何時(shí)開始執(zhí)行再登,完全取決于GCD尔邓。如果一個(gè)代碼塊的執(zhí)行時(shí)間與另一個(gè)重疊了,那也是由GCD來決定是否它應(yīng)該運(yùn)行在另一個(gè)處理器核心锉矢,是一個(gè)核心就夠用了梯嗽,還是要用環(huán)境切換來處理不同的代碼塊。
隊(duì)列類型(Queue Types)
僅僅為了讓事情更有趣(大牛沈撞!這么說話不怕閃著腰嗎慷荔?……)GCD提供了至少五中隊(duì)列類型來選擇。
首先缠俺,系統(tǒng)提供了一個(gè)特殊的串行隊(duì)列——main queue显晶。像其他串行隊(duì)列一樣,該隊(duì)列在同一時(shí)間只能執(zhí)行一個(gè)任務(wù)壹士。然而磷雇,它是唯一一個(gè)允許你更新UI的隊(duì)列。它就是那個(gè)你用來發(fā)送信息給UIView或者發(fā)送通知的隊(duì)列躏救。
系統(tǒng)當(dāng)然也提供了幾個(gè)并發(fā)隊(duì)列唯笙。它們被命名為Global Dispatch Queues
。它們因不同的優(yōu)先級(jí)被分為四種:background
,low
,default
,high
盒使。要知道蘋果的API也在用這些隊(duì)列崩掘,所以,這些隊(duì)列中不會(huì)僅僅有你的任務(wù)少办。
最終苞慢,你還可以定制自己的串行或并發(fā)隊(duì)列。這意味著你最少有五種隊(duì)列可以派遣:main queue(串行)
,4種global dispatch queues(并發(fā))
,你定制的隊(duì)列
英妓。
dispatch_async
當(dāng)你需要進(jìn)行網(wǎng)絡(luò)操作或者計(jì)算密集型任務(wù)時(shí)挽放,應(yīng)考慮用dispatch_async绍赛,使任務(wù)在后臺(tái)執(zhí)行,從而不阻塞當(dāng)前線程辑畦。
在各種隊(duì)列中應(yīng)該怎么用dispatch_async吗蚌,以下是一些建議:
- 定制的串行隊(duì)列(Custom Serial Queue):當(dāng)你想在后臺(tái)執(zhí)行串行隊(duì)列時(shí),
dispatch_async
是個(gè)不錯(cuò)的選擇纯出。因?yàn)榇性谕粫r(shí)間只能執(zhí)行一個(gè)任務(wù)蚯妇,所以它會(huì)消除資源之間的聯(lián)系。要注意潦刃,如果你需要從方法中返回的數(shù)據(jù)侮措,你必須在串行中添加另一個(gè)代碼塊來恢復(fù)數(shù)據(jù)或者考慮用dispatch_sync
。 - 主隊(duì)列(Main Queue):在并發(fā)隊(duì)列中執(zhí)行完任務(wù)乖杠,我們通常會(huì)在主隊(duì)列中刷新UI分扎。就像這樣:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *overlayImage = [self faceOverlayImageFromImage:_image];
dispatch_async(dispatch_get_main_queue(), ^{
[self fadeInNewImage:overlayImage];
});
});
- 并發(fā)隊(duì)列(Concurrent Queue):在后臺(tái)執(zhí)行非UI的操作,dispatch_async是很自然的選擇胧洒。
dispatch_after
dispatch_after就像一個(gè)延遲執(zhí)行的dispatch_async畏吓。
什么樣的隊(duì)列適合使用呢?
- 定制的串行隊(duì)列(Custom Serial Queue):不建議卫漫,延遲操作沒什么意義菲饼。
- 主隊(duì)列(Main Queue):這是最好的選擇。就像這樣:
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); // 1
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // 2
if (!count) {
[self.navigationItem setPrompt:@"Add photos with faces to Googlyify them!"];
} else {
[self.navigationItem setPrompt:nil];
}
});
- 并發(fā)隊(duì)列(Concurrent Queue):沒必要列赎。
單例的線程安全
常見的單例實(shí)現(xiàn)如下:
+ (instancetype)sharedManager
{
static PhotoManager *sharedPhotoManager = nil;
if (!sharedPhotoManager) {
sharedPhotoManager = [[PhotoManager alloc] init];
sharedPhotoManager->_photosArray = [NSMutableArray array];
}
return sharedPhotoManager;
}
這段代碼非常簡(jiǎn)單宏悦,我們創(chuàng)建了一個(gè)單例,并且實(shí)例化了一個(gè)私有的可變數(shù)組photosArray
包吝。
然而饼煞,if語(yǔ)句里的代碼并不是線程安全的。如果連續(xù)多次調(diào)用此方法诗越,有這樣一種可能砖瞧,線程A進(jìn)入if語(yǔ)句,在單例創(chuàng)建完成前嚷狞,發(fā)生環(huán)境切換( context switch),轉(zhuǎn)到線程B块促,線程B也會(huì)進(jìn)入if語(yǔ)句并實(shí)例化此單例,然后系統(tǒng)再次環(huán)境切換到線程A床未,線程A會(huì)繼續(xù)執(zhí)行if語(yǔ)句后面的內(nèi)容竭翠,實(shí)例化另一個(gè)單例。這顯然就不是單例了薇搁。
if語(yǔ)句里的代碼就是危險(xiǎn)區(qū)(Critical Section)斋扰,當(dāng)連續(xù)執(zhí)行:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[PhotoManager sharedManager];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[PhotoManager sharedManager];
});
它會(huì)創(chuàng)建多個(gè)不同的“單例”,這兩個(gè)并發(fā)隊(duì)列存在競(jìng)態(tài)條件(Race Condition)。
所以褥实,dispatch_once
就登場(chǎng)了!
dispatch_once用一種線程安全的方式裂允,執(zhí)行且只執(zhí)行一次代碼塊损离。當(dāng)一個(gè)線程已經(jīng)在執(zhí)行dispatch_once中的危險(xiǎn)區(qū)(critical section),那另一個(gè)試圖進(jìn)入該代碼塊的線程將被阻止绝编,直到前一個(gè)線程執(zhí)行完畢危險(xiǎn)區(qū)中的代碼僻澎。代碼如下:
+ (instancetype)sharedManager
{
static PhotoManager *sharedPhotoManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPhotoManager = [[PhotoManager alloc] init];
sharedPhotoManager->_photosArray = [NSMutableArray array];
});
return sharedPhotoManager;
}
需要指出的是,這僅僅使進(jìn)入單例變得線程安全十饥,并沒有使這個(gè)類完全線程安全窟勃。如果單例中的屬性是一個(gè)可變對(duì)象(如NSMutableArray),那你要考慮這個(gè)對(duì)象本身是否線程安全逗堵。
如果對(duì)象是一個(gè)容器類(array,dictionary)秉氧,那么很可能它是線程不安全的。蘋果提供了一個(gè)線程是否安全的清單汁咏。可以發(fā)現(xiàn)攘滩,NSMutableArray,正是線程不安全的。
我們可能會(huì)這樣用_photosArray
屬性:
- (void)addPhoto:(Photo *)photo
{
if (photo) {
[_photosArray addObject:photo];
dispatch_async(dispatch_get_main_queue(), ^{
[self postContentAddedNotification];
});
}
}
以上是一個(gè)write
方法纸泡,因?yàn)樗淖兞怂接锌勺償?shù)組對(duì)象漂问。
- (NSArray *)photos
{
return [NSArray arrayWithArray:_photosArray];
}
以上是一個(gè)read
方法,因?yàn)樗x取了這個(gè)可變數(shù)組女揭。它做了一份不可變的拷貝(返回了一個(gè)包含同樣對(duì)象的不可變數(shù)組),以防在調(diào)用時(shí)蚤假,不適當(dāng)?shù)牟僮鞲淖兞诉@個(gè)可變數(shù)組。然而當(dāng)一個(gè)線程調(diào)用write方法田绑,另一個(gè)線程調(diào)用read方法時(shí)勤哗,這里并沒有任何的保護(hù)。
盡管很多線程同時(shí)讀取NSMutableArray的實(shí)例不會(huì)有問題掩驱,但是芒划,當(dāng)一個(gè)
線程在改變這個(gè)數(shù)組,而另外一個(gè)讀取的時(shí)候欧穴,就不盡然了民逼。上面的方法并沒有防止這種可能。
這是軟件開發(fā)中典型的讀寫難題(Readers-Writers Problem)涮帘。GCD提供了一種優(yōu)雅的解決方案——通過dispatch barriers
創(chuàng)建讀寫鎖(Readers-writer lock)拼苍。
dispatch barriers是一組函數(shù),當(dāng)它與并發(fā)隊(duì)列一起用時(shí),它就像一個(gè)串行瓶頸疮鲫。用barriers的API能確保提交的代碼塊是在特定時(shí)間指定隊(duì)列中唯一被執(zhí)行的吆你。這意味著先前提交到此隊(duì)列中的代碼塊,要在dispatch barrier執(zhí)行前俊犯,執(zhí)行完畢妇多。如下圖所示:
注意上圖,在barrier執(zhí)行前燕侠,這個(gè)并發(fā)隊(duì)列就像一般的并發(fā)隊(duì)列那樣執(zhí)行者祖。但是,在barrier執(zhí)行后绢彤,這個(gè)并發(fā)隊(duì)列看起來像個(gè)串行隊(duì)列了(所謂的串行瓶頸)七问。即,barrier是唯一一個(gè)被執(zhí)行的代碼塊茫舶。在barrier執(zhí)行完后,這個(gè)隊(duì)列又開始像一般的并發(fā)隊(duì)列一樣坟比。
GCD提供了既有同步葛账,也有異步的barrier函數(shù)皮仁。
在用barrier函數(shù)是應(yīng)注意什么時(shí)候能用贷祈,而什么時(shí)候不能:
- 定制的串行隊(duì)列(Custom Serial Queue):相當(dāng)不建議势誊。本身已經(jīng)串行了,還用barrier干啥查近?
- 系統(tǒng)提供的并發(fā)隊(duì)列(Global Concurrent Queue):要很謹(jǐn)慎的用霜威。因?yàn)橄到y(tǒng)的并發(fā)隊(duì)列中不僅僅運(yùn)行你指定的代碼塊戈泼,當(dāng)你用barrier后扭倾,你會(huì)在barrier執(zhí)行時(shí)挽绩,壟斷這個(gè)線程琼牧,這可能會(huì)帶來不必要的麻煩巨坊。
- 定制的并發(fā)隊(duì)列(Custom Concurrent Queue):這是最好的選擇趾撵。
因?yàn)槎ㄖ频牟l(fā)隊(duì)列是我們最佳的選擇占调,所以究珊,我們最好自己創(chuàng)建一個(gè)并發(fā)隊(duì)列剿涮,來操作barrier函數(shù)取试,實(shí)現(xiàn)分離讀寫操作瞬浓。
@interface PhotoManager ()
@property (nonatomic,strong,readonly) NSMutableArray *photosArray;
@property (nonatomic, strong) dispatch_queue_t concurrentPhotoQueue; ///< 一個(gè)并發(fā)隊(duì)列屬性
@end
+ (instancetype)sharedManager
{
static PhotoManager *sharedPhotoManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPhotoManager = [[PhotoManager alloc] init];
sharedPhotoManager->_photosArray = [NSMutableArray array];
// 初始化并發(fā)隊(duì)列:其中我們習(xí)慣性的用倒轉(zhuǎn)的DNS命名作為第一個(gè)參數(shù)猿棉,在我們調(diào)試的時(shí)候這會(huì)很有幫助;第二個(gè)參數(shù)來指定我們創(chuàng)建串行還是并發(fā)隊(duì)列
sharedPhotoManager->_concurrentPhotoQueue = dispatch_queue_create("com.selander.GooglyPuff.photoQueue",
DISPATCH_QUEUE_CONCURRENT);
});
return sharedPhotoManager;
}
在write中加入barrier:
- (void)addPhoto:(Photo *)photo
{
if (photo) {
dispatch_barrier_async(self.concurrentPhotoQueue, ^{
[_photosArray addObject:photo];
dispatch_async(dispatch_get_main_queue(), ^{
[self postContentAddedNotification];
});
});
}
}
為了write的線程安全位迂,我們需要將read也寫在剛才那個(gè)并發(fā)隊(duì)列中(真心不知道為什么read方法也要寫在這個(gè)線程……)掂林。我們需要從函數(shù)中得到返回值泻帮,所以我們不能用異步。
這一次脂倦,我們可以用dispatch_sync
。
用dispatch_sync一定要小心踱蠢,否則很有可能造成死鎖(deadlock)。如果你把現(xiàn)在正在運(yùn)行的隊(duì)列(串行)作為dispatch_sync的目標(biāo)隊(duì)列苇侵,那就會(huì)死鎖榆浓。dispatch_sync在等待block語(yǔ)句執(zhí)行完哀军,但是block不可能執(zhí)行完(它根本就還沒開始呢)杉适,除非dispatch_sync這條調(diào)用的語(yǔ)句執(zhí)行完猿推,而這是不可能的蹬叭。
所以秽五,你應(yīng)該了解什么時(shí)候坦喘、在哪里答朋,用dispatch_sync:
- 定制的串行隊(duì)列(Custom Serial Queue)在這個(gè)隊(duì)列中梦碗,你得非常小心洪规。如果你在這個(gè)隊(duì)列中斩例,并且調(diào)用dispatch_sync,目標(biāo)是這個(gè)隊(duì)列洋满,那么你就造成了一個(gè)死鎖(deadlock)牺勾。
- 主隊(duì)列(Main Queue):就像在定制的串行隊(duì)列中所說的那樣,也會(huì)造成死鎖履怯。
- 并發(fā)隊(duì)列(Concurrent Queue):無論是用dispatch barriers或者dispatch——sync,這是一個(gè)好選擇叹洲。
代碼如下:
- (NSArray *)photos
{
__block NSArray *array;
dispatch_sync(self.concurrentPhotoQueue, ^{
array = [NSArray arrayWithArray:_photosArray];
});
return array;
}
Dispatch Groups
如果你想確保一組任務(wù)完成运提,再執(zhí)行某些任務(wù)癣丧,那么Dispatch Groups
可以派上用場(chǎng)胁编。Dispatch Groups里的任務(wù)可以是異步的掏呼,也可以是同步的憎夷,并且這些無論同步還是異步的任務(wù)祥得,可以來自不同的隊(duì)列。當(dāng)Dispatch Groups里的任務(wù)執(zhí)行完后蒋得,它可以以異步或者同步的方式通知你级及。
我們需要?jiǎng)?chuàng)建一個(gè)dispatch_group_t
的實(shí)例來追蹤這些在不同隊(duì)列中的任務(wù)。
我們先看其同步的方式——dispatch_group_wait额衙。dispatch_group_wait會(huì)阻塞當(dāng)前的線程饮焦,直到組中每個(gè)任務(wù)都完成或者超時(shí)。
我們以下面的代碼作為例子來講解:
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
//由于我們用了同步的方式dispatch_group_wait窍侧,它會(huì)阻塞當(dāng)前線程县踢,所以我們?cè)谡麄€(gè)方法外面套上了dispatch_async,使它在后臺(tái)執(zhí)行而不會(huì)阻塞主線程伟件。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
__block NSError *error;
//我們創(chuàng)建了一個(gè)dispatch group硼啤,這就像一個(gè)未完成任務(wù)的計(jì)數(shù)器嗓袱。
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
//dispatch_group_enter告知group,一個(gè)任務(wù)開始了。我們需要將dispatch_group_enter與dispatch_group_leave配對(duì)缺菌,否則我們會(huì)遇到莫名其妙的崩潰焊傅。
dispatch_group_enter(downloadGroup);
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
//dispatch_group_leave告知group,此任務(wù)執(zhí)行完畢,要注意與dispatch_group_enter配對(duì)带射。
dispatch_group_leave(downloadGroup);
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
//dispatch_group_wait等待所有任務(wù)完成或者超時(shí)灿里,在此處我們?cè)O(shè)置等待時(shí)間為永遠(yuǎn)DISPATCH_TIME_FOREVER
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER);
//當(dāng)以上所有任務(wù)執(zhí)行完后,我們?cè)谥麝?duì)列調(diào)用任務(wù)完成的block。
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(error);
}
});
});
}
畢竟撵儿,用同步阻塞線程浪默,感覺會(huì)不爽岳链,我們換異步的方式。
dispatch_group_notify
提供了異步完成的block纬向。當(dāng)group中沒有其他任務(wù)時(shí)担孔,它會(huì)執(zhí)行block拌消。當(dāng)然债蓝,我們需要為block指定完成的隊(duì)列啊鸭。在這個(gè)例子中钟些,我們把主隊(duì)列作為參數(shù)。代碼如下:
//用dispatch_group_notify篙耗,就不需要在方法外套著dispatch_async
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup);
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup);
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
//我們異步調(diào)用了group宪赶,并且在group執(zhí)行完后罕伯,返回主隊(duì)列涤妒,執(zhí)行完成后的代碼民褂。
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(error);
}
});
dispatch_apply
dispatch_apply很像一個(gè)for循環(huán)舶衬,但是它并發(fā)的執(zhí)行每一項(xiàng)。不過赎离,dispatch_apply本身像for循環(huán)一樣逛犹,是串行的。當(dāng)所有工作結(jié)束梁剔,dispatch_apply返回虽画。代碼如下:
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
//第一個(gè)參數(shù)是循環(huán)次數(shù),第二個(gè)參數(shù)也可以是串行荣病,但是那就沒有意義了码撰,還不如用for循環(huán),第三個(gè)參數(shù)是增量變量
dispatch_apply(3, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup);
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup);
}];
[[PhotoManager sharedManager] addPhoto:photo];
});
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(error);
}
});
這樣个盆,循環(huán)變成并發(fā)的了脖岛。由于是并發(fā)的,循環(huán)的每一項(xiàng)的完成時(shí)機(jī)變成不可知的(for循環(huán)的時(shí)候是依次執(zhí)行颊亮,所以是順序是可知的)柴梆。
用dispatch_apply替代for是否值得呢?
在以上代碼中终惑,用dispatch_apply并沒有很大的優(yōu)勢(shì)绍在,反而讓隊(duì)列中跑了
很多線程,我們應(yīng)該在處理耗時(shí)較長(zhǎng)的循環(huán)時(shí),使用dispatch_apply偿渡。
啊哈臼寄,已經(jīng)寫了很長(zhǎng)了,GCD很豐富溜宽,還有很多有趣的特性脯厨,例如:semaphores、Dispatch Sources等等坑质,這篇算個(gè)開篇介紹吧合武,高階一點(diǎn)的以后再寫,敬請(qǐng)期待涡扼。