寫在最前邊咒彤,這邊文章適合剛?cè)胄谢蛘邔CD不是很了解的同學(xué)閱讀,大神請略過~~~~~~
為何要寫這篇文章呢咒精?
最近看了好多iOS簡歷镶柱,在專業(yè)技能上都會寫有這么一條** “熟悉NSThread、NSOperation模叙、GCD等多線程開發(fā)技術(shù)” **歇拆,看到這里我都會問同一個(gè)問題,就是 “你對其中哪種技術(shù)最熟悉范咨,或者用的最多”故觅,99%的面試者給出的答案都是GCD。進(jìn)一步詢問使用情況的時(shí)候渠啊,大多數(shù)面試者都會給出比較簡單的答案输吏。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//執(zhí)行耗時(shí)操作
……
dispatch_async(dispatch_get_main_queue(), ^{
//回到主線程進(jìn)行UI刷新操作
};
};
然后就基本沒有然后了。這篇文章我會針對GCD一些常用的方法做一下簡要分析(太復(fù)雜的方法我也不會~~~)替蛉,希望能對剛?cè)胄械耐瑢W(xué)們有一定的幫助贯溅。
串行 & 并行
GCD中dispatch_queue大致可以分為三類
- 全局的并行的queue
- 主線程的串行的queue
- 自定義的queue
全局的queue和主線程的queue結(jié)合使用(上邊提到的)就是我們平常最常用的一種用法,在異步線程中執(zhí)行耗時(shí)操作躲查,然后在UI線程執(zhí)行刷新操作它浅。
全局的queue我們可以通過dispatch_get_global_queue(0, 0)直接獲取,這里有兩個(gè)參數(shù)镣煮,第一個(gè)表示線程執(zhí)行的優(yōu)先級(第二個(gè)參數(shù)是預(yù)留參數(shù)暫時(shí)沒有什么鳥用)姐霍,什么意思呢?當(dāng)我們通過dispatch_async(globalQueue, ^{}); 這種方式去異步執(zhí)行一個(gè)操作時(shí)怎静,實(shí)際上操作系統(tǒng)會創(chuàng)建一個(gè)新的線程邮弹,當(dāng)我們同時(shí)執(zhí)行多個(gè)這樣的操作時(shí),我們?nèi)绾文鼙WC哪個(gè)線程先執(zhí)行呢蚓聘?這時(shí)候這個(gè)參數(shù)就派上了用場腌乡。這個(gè)參數(shù)一共可以有四個(gè)值:
#######define DISPATCH_QUEUE_PRIORITY_HIGH 2
#######define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#######define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#######define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
</code>
下面來看一段代碼,我們設(shè)置默認(rèn)值0
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 1");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 2");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task 3");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 3");
});
接下來我們加入優(yōu)先級的參數(shù)夜牡,再來看一下結(jié)果
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 1");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 2");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"start task 3");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 3");
});
結(jié)果很明顯已經(jīng)完全按照我們的優(yōu)先級進(jìn)行執(zhí)行的
DISPATCH_QUEUE_PRIORITY_HIGH>DISPATCH_QUEUE_PRIORITY_DEFAULT>DISPATCH_QUEUE_PRIORITY_LOW
相信大家已經(jīng)明白第一個(gè)參數(shù)的作用了吧与纽,如果你的需求需要某個(gè)并發(fā)線程先執(zhí)行,可以通過設(shè)置優(yōu)先級來達(dá)到目的塘装,但是因?yàn)榫€程是并發(fā)執(zhí)行的急迂,所以你不能保證哪個(gè)線程會先執(zhí)行完,也就是不能保證我們的耗時(shí)任務(wù)是按照順序執(zhí)行的蹦肴。
那么如何才能保證按順序執(zhí)行呢僚碎?這就需要我們自定義串行的queue來解決,系統(tǒng)為我們提供了這個(gè)方法dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)阴幌,這個(gè)方法同樣也有兩個(gè)參數(shù)勺阐,第一個(gè)參數(shù)是確定唯一queue的一個(gè)標(biāo)識卷中,第二個(gè)參數(shù)創(chuàng)建queue的類型,串行的還是并行的渊抽。下面看一個(gè)例子:
/**
* 我們創(chuàng)建了一個(gè)串行的queue
*
* @param "com.gcd.test.queue" 唯一標(biāo)識這個(gè)queue
* @param DISPATCH_QUEUE_SERIAL 說明是串行的queue
*/
dispatch_queue_t myQueue = dispatch_queue_create("com.gcd.test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(myQueue, ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 1");
});
dispatch_async(myQueue, ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 2");
});
dispatch_async(myQueue, ^{
NSLog(@"start task 3");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 3");
});
有兩點(diǎn)需要注意的地方蟆豫,第一,串行queue嚴(yán)格按照順序執(zhí)行耗時(shí)任務(wù)懒闷。第二十减,GCD在執(zhí)行串行queue時(shí),其實(shí)是在一個(gè)線程中完成的愤估,這樣就會嚴(yán)格的按照順序進(jìn)行執(zhí)行了帮辟,如上圖中紅色部分,42565是主線程ID灵疮, 1589679就是我們的異步線程ID织阅。而在并發(fā)的queue中,我們會發(fā)現(xiàn)線程ID都是不一樣的震捣,說明是多個(gè)線程。
如果我們在queue的創(chuàng)建時(shí)dispatch_queue_t myQueue = dispatch_queue_create("com.gcd.test.queue", DISPATCH_QUEUE_SERIAL)闹炉,我們把參數(shù)DISPATCH_QUEUE_SERIAL變成DISPATCH_QUEUE_CONCURRENT蒿赢,將會創(chuàng)建一個(gè)并發(fā)的queue,此處的結(jié)果和global_queue無異渣触,這里不在贅述羡棵。
dispatch_group_queue
GCD另一個(gè)比較常用的方法就是dispatch_group_queue,
在我們平時(shí)實(shí)際項(xiàng)目中經(jīng)常會有這樣的需求嗅钻,就是在多個(gè)任務(wù)異步處理后我們需要一個(gè)統(tǒng)一的回調(diào)通知去處理接下來的業(yè)務(wù)皂冰,這個(gè)時(shí)候我們就想到了dispatch group了,當(dāng)所有任務(wù)完成后會調(diào)用dispatch_group_notify养篓,來看一個(gè)例子:
/**
* 創(chuàng)建一個(gè)并發(fā)的queue
*/
dispatch_queue_t queue = dispatch_queue_create("com.test.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
/**
* 創(chuàng)建一個(gè)group
*/
dispatch_group_t group = dispatch_group_create();
/**
* 執(zhí)行3個(gè)耗時(shí)任務(wù)
*/
dispatch_group_async(group, queue, ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"start task 3");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 3");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"task over");
});
當(dāng)三個(gè)異步耗時(shí)操作完成后秃流,統(tǒng)一一個(gè)回調(diào),兩點(diǎn)注意:
- 回調(diào)回來的數(shù)據(jù)異步線程中柳弄,這一點(diǎn)通過它前邊的線程ID號1631915就能發(fā)現(xiàn)舶胀。所以如果后續(xù)執(zhí)行刷新UI操作需要到主線程中完成。
- 接收回調(diào)的這個(gè)線程就是任務(wù)最后執(zhí)行完的那個(gè)線程碧注,系統(tǒng)做了優(yōu)化并沒有多開一個(gè)線程來處理嚣伐。
結(jié)合實(shí)際場景,很多同學(xué)比較容易犯一個(gè)錯(cuò)誤萍丐,假如我們有兩個(gè)請求需要同時(shí)發(fā)送轩端,并統(tǒng)一回調(diào),很多同學(xué)這時(shí)候就會想到了dipatch_group逝变。
dispatch_queue_t queue = dispatch_queue_create("com.test.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[self sendRequest1:^{
}];
});
dispatch_group_async(group, queue, ^{
[self sendRequest2:^{
}];
});
dispatch_group_notify(group, queue, ^{
NSLog(@"task over");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"refresh ui");
});
});
- (void)sendRequest1:(void(^)())block {
//異步請求基茵,請求結(jié)果后block回調(diào)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 1");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
- (void)sendRequest2:(void(^)())block {
//異步請求奋构,請求結(jié)果后block回調(diào)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:3];
NSLog(@"end task 2");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
其中sendRequest1和sendRequest2是兩個(gè)異步請求,可以簡單理解為我們的業(yè)務(wù)請求API耿导,我們最初的設(shè)想是task1和task2完成后統(tǒng)一回調(diào)回來声怔,但結(jié)果確實(shí)這樣:
這是為什么呢?很明顯應(yīng)為sendRequest1和sendRequest2都是異步請求API舱呻,所以dispatch_group_async中瞬間就執(zhí)行完了醋火。腫么辦?難道針對這種需求就沒有辦法了嗎箱吕?當(dāng)然有芥驳,dispatch_group還有一個(gè)辦法能很好的解決這個(gè)問題,就是dispatch_group_enter() & dispatch_group_leave這對組合茬高。我們把這個(gè)需求重新實(shí)現(xiàn)一下:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self sendRequest1:^{
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self sendRequest2:^{
dispatch_group_leave(group);
}];
dispatch_group_notify(group, queue, ^{
NSLog(@"task over");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"refresh ui");
});
});
這下就對了兆旬,完全按照我們的想法去執(zhí)行了。
dispatch_group_enter() 和 dispatch_group_leave()必須成對出現(xiàn)怎栽,group_enter是將請求任務(wù)放入到group后丽猬,便一直被group持有,直到碰到group_leave熏瞄;才會釋放出來脚祟,只有g(shù)roup中不在持有任何任務(wù)后才會調(diào)用notify進(jìn)行回調(diào)通知。
文章寫到這里基本已經(jīng)接近尾聲了强饮,當(dāng)然GCD還有很多其他用法由桌,常用的比如dispatch_once、dispatch_after邮丰、dispatch_barrier等等行您,本篇文章就不在做詳細(xì)說明。希望以上的內(nèi)容能對正在閱讀的你有所幫助剪廉。