【Objective-C】GCD介紹

整理自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ò)覺拟逮,就像下圖展示的:

ConcurrencyVSParallelism
ConcurrencyVSParallelism

盡管你在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í)間堪侯,就像下圖所示:

SerialQueues
SerialQueues

這些任務(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í)行的例子:

ConcurrentQueues
ConcurrentQueues

從圖中發(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í)行完畢妇多。如下圖所示:

DispatchBarriers
DispatchBarriers

注意上圖,在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)期待涡扼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末稼跳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吃沪,更是在濱河造成了極大的恐慌汤善,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件票彪,死亡現(xiàn)場(chǎng)離奇詭異红淡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)降铸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門在旱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人推掸,你說我怎么就攤上這事桶蝎。” “怎么了谅畅?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵登渣,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我毡泻,道長(zhǎng)胜茧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任仇味,我火速辦了婚禮呻顽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘邪铲。我一直安慰自己芬位,他們只是感情好无拗,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布带到。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪揽惹。 梳的紋絲不亂的頭發(fā)上被饿,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音搪搏,去河邊找鬼狭握。 笑死,一個(gè)胖子當(dāng)著我的面吹牛疯溺,可吹牛的內(nèi)容都是我干的论颅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼囱嫩,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼恃疯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起墨闲,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤今妄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鸳碧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盾鳞,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年瞻离,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腾仅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡套利,死狀恐怖攒砖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情日裙,我是刑警寧澤吹艇,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站昂拂,受9級(jí)特大地震影響受神,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜格侯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一鼻听、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧联四,春花似錦撑碴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春亿卤,著一層夾襖步出監(jiān)牢的瞬間愤兵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工排吴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秆乳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓钻哩,卻偏偏與公主長(zhǎng)得像屹堰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子街氢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容