寫在最前邊,這邊文章適合剛?cè)胄谢蛘邔CD不是很了解的同學閱讀,大神請略過~~~~~~
為何要寫這篇文章呢?
最近看了好多iOS簡歷,在專業(yè)技能上都會寫有這么一條** “熟悉NSThread、NSOperation、GCD等多線程開發(fā)技術(shù)” **,看到這里我都會問同一個問題,就是 “你對其中哪種技術(shù)最熟悉,或者用的最多”,99%的面試者給出的答案都是GCD。進一步詢問使用情況的時候,大多數(shù)面試者都會給出比較簡單的答案。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//執(zhí)行耗時操作
……
dispatch_async(dispatch_get_main_queue(), ^{
//回到主線程進行UI刷新操作
};
};
然后就基本沒有然后了。這篇文章我會針對GCD一些常用的方法做一下簡要分析(太復雜的方法我也不會~~~),希望能對剛?cè)胄械耐瑢W們有一定的幫助。
串行 & 并行
GCD中dispatch_queue大致可以分為三類
- 全局的并行的queue
- 主線程的串行的queue
- 自定義的queue
全局的queue和主線程的queue結(jié)合使用(上邊提到的)就是我們平常最常用的一種用法,在異步線程中執(zhí)行耗時操作,然后在UI線程執(zhí)行刷新操作。
全局的queue我們可以通過dispatch_get_global_queue(0, 0)直接獲取,這里有兩個參數(shù),第一個表示線程執(zhí)行的優(yōu)先級(第二個參數(shù)是預留參數(shù)暫時沒有什么鳥用),什么意思呢?當我們通過dispatch_async(globalQueue, ^{}); 這種方式去異步執(zhí)行一個操作時,實際上操作系統(tǒng)會創(chuàng)建一個新的線程,當我們同時執(zhí)行多個這樣的操作時,我們?nèi)绾文鼙WC哪個線程先執(zhí)行呢?這時候這個參數(shù)就派上了用場。這個參數(shù)一共可以有四個值:
#######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è)置默認值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)先級進行執(zhí)行的
DISPATCH_QUEUE_PRIORITY_HIGH>DISPATCH_QUEUE_PRIORITY_DEFAULT>DISPATCH_QUEUE_PRIORITY_LOW
相信大家已經(jīng)明白第一個參數(shù)的作用了吧,如果你的需求需要某個并發(fā)線程先執(zhí)行,可以通過設(shè)置優(yōu)先級來達到目的,但是因為線程是并發(fā)執(zhí)行的,所以你不能保證哪個線程會先執(zhí)行完,也就是不能保證我們的耗時任務(wù)是按照順序執(zhí)行的。
那么如何才能保證按順序執(zhí)行呢?這就需要我們自定義串行的queue來解決,系統(tǒng)為我們提供了這個方法dispatch_queue_create(const char *label, dispatch_queue_attr_t attr),這個方法同樣也有兩個參數(shù),第一個參數(shù)是確定唯一queue的一個標識,第二個參數(shù)創(chuàng)建queue的類型,串行的還是并行的。下面看一個例子:
/**
* 我們創(chuàng)建了一個串行的queue
*
* @param "com.gcd.test.queue" 唯一標識這個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");
});
有兩點需要注意的地方,第一,串行queue嚴格按照順序執(zhí)行耗時任務(wù)。第二,GCD在執(zhí)行串行queue時,其實是在一個線程中完成的,這樣就會嚴格的按照順序進行執(zhí)行了,如上圖中紅色部分,42565是主線程ID, 1589679就是我們的異步線程ID。而在并發(fā)的queue中,我們會發(fā)現(xiàn)線程ID都是不一樣的,說明是多個線程。
如果我們在queue的創(chuàng)建時dispatch_queue_t myQueue = dispatch_queue_create("com.gcd.test.queue", DISPATCH_QUEUE_SERIAL),我們把參數(shù)DISPATCH_QUEUE_SERIAL變成DISPATCH_QUEUE_CONCURRENT,將會創(chuàng)建一個并發(fā)的queue,此處的結(jié)果和global_queue無異,這里不在贅述。
dispatch_group_queue
GCD另一個比較常用的方法就是dispatch_group_queue,
在我們平時實際項目中經(jīng)常會有這樣的需求,就是在多個任務(wù)異步處理后我們需要一個統(tǒng)一的回調(diào)通知去處理接下來的業(yè)務(wù),這個時候我們就想到了dispatch group了,當所有任務(wù)完成后會調(diào)用dispatch_group_notify,來看一個例子:
/**
* 創(chuàng)建一個并發(fā)的queue
*/
dispatch_queue_t queue = dispatch_queue_create("com.test.gcd.queue", DISPATCH_QUEUE_CONCURRENT);
/**
* 創(chuàng)建一個group
*/
dispatch_group_t group = dispatch_group_create();
/**
* 執(zhí)行3個耗時任務(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");
});
當三個異步耗時操作完成后,統(tǒng)一一個回調(diào),兩點注意:
- 回調(diào)回來的數(shù)據(jù)異步線程中,這一點通過它前邊的線程ID號1631915就能發(fā)現(xiàn)。所以如果后續(xù)執(zhí)行刷新UI操作需要到主線程中完成。
- 接收回調(diào)的這個線程就是任務(wù)最后執(zhí)行完的那個線程,系統(tǒng)做了優(yōu)化并沒有多開一個線程來處理。
結(jié)合實際場景,很多同學比較容易犯一個錯誤,假如我們有兩個請求需要同時發(fā)送,并統(tǒng)一回調(diào),很多同學這時候就會想到了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是兩個異步請求,可以簡單理解為我們的業(yè)務(wù)請求API,我們最初的設(shè)想是task1和task2完成后統(tǒng)一回調(diào)回來,但結(jié)果確實這樣:
這是為什么呢?很明顯應(yīng)為sendRequest1和sendRequest2都是異步請求API,所以dispatch_group_async中瞬間就執(zhí)行完了。腫么辦?難道針對這種需求就沒有辦法了嗎?當然有,dispatch_group還有一個辦法能很好的解決這個問題,就是dispatch_group_enter() & dispatch_group_leave這對組合。我們把這個需求重新實現(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進行回調(diào)通知。
文章寫到這里基本已經(jīng)接近尾聲了,當然GCD還有很多其他用法,常用的比如dispatch_once、dispatch_after、dispatch_barrier等等,本篇文章就不在做詳細說明。希望以上的內(nèi)容能對正在閱讀的你有所幫助。