3.1 Grand Central Dispatch(GCD)概要
3.1.1 什么是CGD
Grand Central dispatch(GCD)是異步執(zhí)行任務(wù)的技術(shù)之一萍程。一般將應(yīng)用程序中記述的線程管理用的代碼在系統(tǒng)級中實(shí)現(xiàn)幢妄。開發(fā)者只需要定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)膁ispatch Queue中,GCD就能生成必要的線程并計(jì)劃執(zhí)行任務(wù)茫负。由于線程管理是作為系統(tǒng)的一部分來實(shí)現(xiàn)的蕉鸳,因此可以統(tǒng)一管理,也可執(zhí)行任務(wù)忍法,這樣就比以前的線程更加有效率潮尝。
也就是說,GCD使用簡潔的記述方法饿序,實(shí)現(xiàn)了復(fù)雜繁瑣的多線程編程??:
dispatch_async(queue, ^{
// 執(zhí)行長時(shí)間網(wǎng)絡(luò)處理
dispatch_async(dispatch_get_main_queue(), ^{
//只在主線程可以執(zhí)行的處理
// 主界面做的事勉失,例如用戶刷新
});
});
等同于NSObject類的performSelectorInBackground實(shí)例方法和performSelectorOnMainThread實(shí)例方法:
- (void)launchThreadByNSObject_performSelectorInBackground_withObject
{
// 執(zhí)行長時(shí)間網(wǎng)絡(luò)處理
[self performSelectorInBackground:@selector(dowork) withObject:nil];
}
- (void)dowork
{
[self performSelectorOnMainThread:@selector(donework) withObject:nil waitUntilDone:NO];
}
- (void)donework
{
//只在主線程可以執(zhí)行的處理
// 主界面做的事,例如用戶刷新
}
由上可知原探,使用CGD乱凿,通過 GCD 提供的系統(tǒng)級線程管理提高執(zhí)行效率顽素。
3.1.2 多線程編程
- 線程(thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。
Mac徒蟆、iPhone的操作系統(tǒng)OS X胁出、iOS根據(jù)用戶的指示啟動應(yīng)用程序后,首先便將包含在應(yīng)用程序中的CPU命令列配置到內(nèi)存中段审。CPU從應(yīng)用程序指定的地址開始全蝶,一個(gè)一個(gè)地執(zhí)行CPU命令列。由于一個(gè)CPU一次只能執(zhí)行一個(gè)命令戚哎,不能執(zhí)行某處分開的并列的兩個(gè)命令裸诽,因此通過CPU執(zhí)行的CPU命令列就是一條無分叉的路。這里所說的“1個(gè)CPU執(zhí)行的CPU名列為一條無分叉的路”就是“線程”型凳。
-
現(xiàn)在一個(gè)物理的CPU芯片實(shí)際上有64(64核)個(gè)CPU丈冬,即可以擁有多條線程。
-
在iOS開發(fā)中甘畅,一共有四種多線程技術(shù):pthread埂蕊,NSThread,GCD疏唾,NSOperation:
- pthread 蓄氧,NSThread是面向線程開發(fā)的多線程技術(shù),需要開發(fā)者自己去維護(hù)線程的生命周期槐脏,比較繁瑣喉童。
GCD, NSOperation是面向隊(duì)列開發(fā)的多線程技術(shù)顿天,開發(fā)者僅僅定義想執(zhí)行的任務(wù)追加到適當(dāng)?shù)腄ispatch Queue(隊(duì)列)中并設(shè)置一些優(yōu)先級堂氯,依賴等操作就可以了,其他的事情可以交給系統(tǒng)來做牌废。
-
多線程編程會產(chǎn)生很多編程技術(shù)問題:數(shù)據(jù)競爭咽白、死鎖、內(nèi)存消耗等問題鸟缕。
-
盡管多線程編程極易發(fā)生各種問題晶框,但是使用多線程編程可保證應(yīng)用程序的響應(yīng)性能。
多線程編程的優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):適當(dāng)提高程序的執(zhí)行效率懂从;適當(dāng)提高資源利用率(CPU/內(nèi)存利用率)授段;
- 缺點(diǎn):開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用1M番甩,子線程占用512KB)畴蒲,如果開啟大量的線程,會占用大量的內(nèi)存空間对室,降低程序的性能模燥;
#注意#:
1. 不要同時(shí)開太多線程(1~3條線程即可,不要超過5條);
2. 線程概念:
- 主線程:UI線程掩宜,顯示蔫骂、刷新UI界面,處理UI控件事件牺汤;
- 子線程:后臺線程辽旋,異步線程;
3 .不要把耗時(shí)操作放在主線程檐迟,要放在子線程中執(zhí)行
- 想要更加了解線程补胚、進(jìn)程等可自行參閱《操作系統(tǒng)原理》這本書。
3.2 GCD的API
3.2.1 Dispatch Queue
“開發(fā)者要做的只是定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)腄ispatch Queue中”追迟。
用源碼表示如下:
dispatch_async(queue, ^{
/*
* 想執(zhí)行的任務(wù)
*/
});
該源碼用block的語法定義想執(zhí)行的任務(wù)然后通過dispatch_async函數(shù)講任務(wù)追加到賦值在變量queue的"Dispatch Queue"中溶其。
Dispatch Queue是什么呢?
-
Dispatch Queue是執(zhí)行處理的等待隊(duì)列, 按照先進(jìn)先出(FIFO, First-In-First-Out)的順序進(jìn)行任務(wù)處理.
-
Dispatch Queue隊(duì)列分兩種, 一種是串行隊(duì)列(Serial Dispatch Queue), 一種是并行隊(duì)列(Concurrent Dispatch Queue)敦间。
- Serial dispatch Queue是等待執(zhí)行完成后才開始下一個(gè)處理瓶逃。
// 創(chuàng)建一個(gè)Serial dispatch Queue
dispatch_queue_t serialdispatchQueue =dispatch_queue_create("com.Sky.serialTest", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialdispatchQueue, ^{NSLog(@"serialdispatchQueue %@", [NSThread currentThread]);});
dispatch_async(serialdispatchQueue, ^{NSLog(@"serialdispatchQueue %@", [NSThread currentThread]);});
dispatch_async(serialdispatchQueue, ^{NSLog(@"serialdispatchQueue %@", [NSThread currentThread]);});
dispatch_async(serialdispatchQueue, ^{NSLog(@"serialdispatchQueue %@", [NSThread currentThread]);});
執(zhí)行結(jié)果如下:
serialdispatchQueue <NSThread: 0x600000675700>{number = 3, name = (null)}
serialdispatchQueue <NSThread: 0x60800087ef00>{number = 3, name = (null)}
serialdispatchQueue <NSThread: 0x60000126e4c0>{number =3, name = (null)}
serialdispatchQueue <NSThread: 0x60800087ef80>{number =3, name = (null)}
- Concurrent dispatch Queue是不等待執(zhí)行,那么任何任務(wù)添加到Concurrent dispatch Queue后廓块,就會立即執(zhí)行厢绝。Concurrent dispatch Queue并行執(zhí)行的處理的數(shù)量取決于當(dāng)前系統(tǒng)的狀態(tài)。
// 創(chuàng)建一個(gè)Concurrent dispatch Queue
dispatch_queue_t concurrentdispatchQueue = dispatch_queue_create("com.Sky.serialTest", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentdispatchQueue, ^{NSLog(@"concurrentdispatchQueue %@", [NSThread currentThread]);});
dispatch_async(concurrentdispatchQueue, ^{NSLog(@"concurrentdispatchQueue %@", [NSThread currentThread]);});
dispatch_async(concurrentdispatchQueue, ^{NSLog(@"concurrentdispatchQueue %@", [NSThread currentThread]);});
dispatch_async(concurrentdispatchQueue, ^{NSLog(@"concurrentdispatchQueue %@", [NSThread currentThread]);});
執(zhí)行結(jié)果如下:
concurrentdispatchQueue <NSThread: 0x600000675700>{number = 3, name = (null)}
concurrentdispatchQueue <NSThread: 0x60800087ef00>{number = 5, name = (null)}
concurrentdispatchQueue <NSThread: 0x60000126e4c0>{number = 6, name = (null)}
concurrentdispatchQueue <NSThread: 0x60800087ef80>{number =4, name = (null)}
3.2.1 dispatch_queue_create
在Dispatch Queue中带猴,我們使用了dispatch_queue_create來建立一個(gè)Queue昔汉。
生成Dispatch Queue有兩種方法:
- 通過GCD的API生成dispatch Queue
- 獲取系統(tǒng)標(biāo)準(zhǔn)提供的dispatch Queue
使用GCD的API生成dispatch_queue_create:
// 創(chuàng)建一個(gè)Concurrent dispatch Queue
dispatch_queue_t concurrentdispatchQueue = dispatch_queue_create("com.Sky.serialTest", DISPATCH_QUEUE_CONCURRENT);
// 創(chuàng)建一個(gè)Serial dispatch Queue
dispatch_queue_t serialdispatchQueue = dispatch_queue_create("com.Sky.serialTest", DISPATCH_QUEUE_SERIAL);
其中,dispatch_queue_create函數(shù)有2個(gè)參數(shù):
- 第一個(gè)參數(shù)是dispatch Queue的名稱拴清。該名稱在Xcode和Instruments的調(diào)試器中作為dispatch Queue的名稱表示靶病。
- 第二個(gè)參數(shù)是指定dispatch Queue的類型,DISPATCH_QUEUE_CONCURRENT 和DISPATCH_QUEUE_SERIAL贷掖。如果填寫NULL則默認(rèn)為DISPATCH_QUEUE_SERIAL嫡秕。
多個(gè)Serial dispatch Queue可并行執(zhí)行!
// 創(chuàng)建一個(gè)SerialQueue
dispatch_queue_t serialdispatchQueue1 = dispatch_queue_create("com.Sky.serialTest", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialdispatchQueue2 = dispatch_queue_create("com.Sky.serialTest", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialdispatchQueue3 = dispatch_queue_create("com.Sky.serialTest", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialdispatchQueue4 = dispatch_queue_create("com.Sky.serialTest", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialdispatchQueue1, ^{NSLog(@"serialdispatchQueue1 %@", [NSThread currentThread]);});
dispatch_async(serialdispatchQueue2, ^{NSLog(@"serialdispatchQueue2 %@", [NSThread currentThread]);});
dispatch_async(serialdispatchQueue3, ^{NSLog(@"serialdispatchQueue3 %@", [NSThread currentThread]);});
dispatch_async(serialdispatchQueue4, ^{NSLog(@"serialdispatchQueue4 %@", [NSThread currentThread]);});
執(zhí)行結(jié)果如下:
serialdispatchQueue1 <NSThread: 0x600000675700>{number = 3, name = (null)}
serialdispatchQueue3 <NSThread: 0x60800087ef00>{number = 5, name = (null)}
serialdispatchQueue4 <NSThread: 0x60000126e4c0>{number = 6, name = (null)}
serialdispatchQueue2 <NSThread: 0x60800087ef80>{number =4, name = (null)}
dispatch_queue_create使用時(shí)應(yīng)注意:
-
不能無限制的創(chuàng)建Serial Dispatch Queue,會消耗大量的內(nèi)存苹威,引起大量的上下文切換昆咽,大幅度降低系統(tǒng)的響應(yīng)性能。
-
在為了避免多個(gè)線程對同一個(gè)資源進(jìn)行操作時(shí)(數(shù)據(jù)競爭)使用Serial Dispatch Queue牙甫,因?yàn)槠涫褂靡粋€(gè)線程掷酗,數(shù)據(jù)安全。
- 當(dāng)不需要顧忌數(shù)據(jù)競爭問題時(shí)候窟哺,推薦使用Concurrent Dispatch Queue泻轰。因?yàn)椴还苌啥嗌伲到y(tǒng)會對其進(jìn)行管理且轨,不用擔(dān)心Serial Dispatch Queue類似的問題浮声。
最好為每一個(gè)Dispatch Queue編寫不同的名字虚婿,否則你會在調(diào)試多線程程序的時(shí)候會后悔沒有為Dispatch Queue署名。
關(guān)于 dispatch_retain和dispatch_release 的使用
如果你部署的最低目標(biāo)低于 iOS 6.0 or Mac OS X 10.8泳挥,你應(yīng)該自己管理GCD對象,使用
(dispatch_retain,dispatch_release),ARC并不會去管理它們然痊。如果你部署的最低目標(biāo)是 iOS 6.0 or Mac OS X 10.8或者更高,ARC已經(jīng)能夠管理GCD對象了,這時(shí)候,GCD對象就如同普通的OC對象一樣,不應(yīng)該使用dispatch_retain或者dispatch_release屉符。
3.2.3 Main Dispatch Queue/Global Dispatch Queue
生成Dispatch Queue的另一種方法:
通過系統(tǒng)標(biāo)準(zhǔn)提供的Dispatch Queue:Main Dispatch Queue剧浸、Global Dispatch Queue.
-
Main Dispatch Queue,即主線程中執(zhí)行的Dispatch Queue,而主線程只有一個(gè)矗钟,所以Main Dispatch Queue就是Serial Dispatch Queue.
/** Main Dispatch Queue的獲取 */
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
-
Global Dispatch Queue,是所有應(yīng)用程序都能夠使用的Concurrent Dispatch Queue唆香。所以沒有必要通過dispatch_queue_create來創(chuàng)建,直接獲取Global Dispatch Queue即可吨艇。
/** Global Dispatch Queue(最高)的獲取方法 */
dispatch_queue_t highGlobalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
/** Global Dispatch Queue(默認(rèn))的獲取方法 */
dispatch_queue_t defaultGlobalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/** Global Dispatch Queue(低)的獲取方法 */
dispatch_queue_t losGlobalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
/** Global Dispatch Queue(后臺躬它,最低)的獲取方法 */
dispatch_queue_t backgroundGlobalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
Global Dispatch Queue有4個(gè)優(yōu)先級,分別是高優(yōu)先級秸应、默認(rèn)優(yōu)先級虑凛、低優(yōu)先級和后臺優(yōu)先級。
3.2.4 dispatch_set_target_queue
dispatch_queue_create函數(shù)生成的兩種Dispatch Queue的優(yōu)先級都與默認(rèn)優(yōu)先級Global Dispatch Queue相同软啼。如果需要修改隊(duì)列的優(yōu)先級桑谍,可以使用 dispatch_set_target_queue 改變順序。
// 創(chuàng)建了默認(rèn)優(yōu)先級的Serial Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.Sky.serialTest", NULL);
// 獲取一個(gè)低優(yōu)先級Concurrent Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
// 將Serial Queue優(yōu)先級變?yōu)榈? dispatch_set_target_queue(serialQueue, globalQueue);
dispatch_set_target_queue函數(shù)有兩個(gè)參數(shù):
- 第一個(gè)參數(shù)是要設(shè)置優(yōu)先級的隊(duì)列祸挪。
- 第二隊(duì)參數(shù)是則是參考的的隊(duì)列锣披,使第一個(gè)參數(shù)與第二個(gè)參數(shù)具有相同的優(yōu)先級。
??:
dispatch_queue_t queue0 = dispatch_queue_create("com.test.queue0", NULL);
dispatch_queue_t queue1 = dispatch_queue_create("com.test.queue1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("com.test.queue2", NULL);
dispatch_async(queue0, ^{
NSLog(@"queue0");
});
dispatch_async(queue1, ^{
NSLog(@"queue1");
});
dispatch_async(queue2, ^{
NSLog(@"queue2");
});
執(zhí)行結(jié)果如下:
queue2
queue0
queue1
使用dispatch_set_target_queue 之后:
dispatch_queue_t queue0 = dispatch_queue_create("com.test.queue0", NULL);
dispatch_queue_t queue1 = dispatch_queue_create("com.test.queue1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("com.test.queue2", NULL);
dispatch_set_target_queue(queue1, queue0);
dispatch_set_target_queue(queue2, queue0);
dispatch_async(queue0, ^{
NSLog(@"queue0");
});
dispatch_async(queue1, ^{
NSLog(@"queue1");
});
dispatch_async(queue2, ^{
NSLog(@"queue2");
});
執(zhí)行結(jié)果如下:
queue0
queue1
queue2
通過上述對比可知贿条,如果在必須將不可并行執(zhí)行的處理追加到多個(gè)Serial Dispatch Queue中雹仿,可使用dispatch_set_target_queue函數(shù)指定執(zhí)行順序,防止處理并行執(zhí)行整以。
3.2.5 dispatch_after
dispatch_after API :指定時(shí)間追加到 Dispatch Queue胧辽。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited at least three seconds");
});
dispatch_after函數(shù)有三個(gè)參數(shù):
- 第一個(gè)參數(shù)是由dispatch_time函數(shù)生成的dispatch_time_t類型的參數(shù)。
dispatch_time函數(shù)獲取的是從第一個(gè)參數(shù)指定的時(shí)間開始公黑,到第二個(gè)參數(shù)指定的毫微秒單位時(shí)間后的時(shí)間邑商。
上面的代碼中第一個(gè)參數(shù)用的DISPATCH_TIME_NOW,表示從現(xiàn)在開始凡蚜,第二個(gè)參數(shù)是2ull*NSEC_PER_SEC人断,表示延遲3秒,合起來就是從現(xiàn)在開始3秒后將Block追加到Main Dispatch Queue中朝蜘。
ull 是 C 語言的數(shù)值字面量恶迈,表示“unsigned long long”
數(shù)值和NSEC_PER_SEC 的乘積得到的單位為毫微秒的數(shù)值。
第二個(gè)參數(shù)是要追加的Dispatch Queue
第三個(gè)參數(shù)則是需要執(zhí)行的Block
在10秒后執(zhí)行響鈴??:
__block NSDate *sendDate=[NSDate date]; // 獲得當(dāng)前時(shí)間
NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init]; // 設(shè)置時(shí)間格式
[dateformatter setDateFormat:@"HH:mm"];
NSString *timeBegain = [dateformatter stringFromDate:sendDate];
NSLog(@"開始時(shí)間:%@",timeBegain);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
sendDate = [NSDate date];
NSString *timeEnd = [dateformatter stringFromDate:sendDate];
NSLog(@"鬧鐘響時(shí)間:%@",timeEnd);
});
執(zhí)行結(jié)果如下:
開始時(shí)間:16:50:00
鬧鐘響時(shí)間:16:50:11
注意:dispatch_after是在指定時(shí)間之后將處理添加到queue中谱醇,但是在什么時(shí)候執(zhí)行需要看queue中的情況暇仲!所以上述執(zhí)行為11s是OK的
3.2.6 Dispatch Group
當(dāng)追加到Dispatch Queue中的多個(gè)處理全部執(zhí)行結(jié)束之后步做,我們通常都會需要執(zhí)行結(jié)束處理。當(dāng)使用Serial Queue的時(shí)候很簡單奈附,只需要將全部處理添加到一個(gè)Serial Queue中辆床,結(jié)束處理在最后添加就可以實(shí)現(xiàn)。但是使用Concurrent Queue的時(shí)候桅狠,就很復(fù)雜。所以我們需要利用Dispatch Group轿秧。
例如中跌,追加3個(gè)處理到Global Queue(Concurrent Queue)中,在3個(gè)處理結(jié)束后執(zhí)行結(jié)束處理:
// 創(chuàng)建一個(gè)Serial Queue,用于執(zhí)行完成處理
dispatch_queue_t serialQueue = dispatch_queue_create("com.Sky.serialTest", NULL);
// 創(chuàng)建默認(rèn)優(yōu)先級Concurrent Queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
// 創(chuàng)建Dispatch Group
dispatch_group_t group = dispatch_group_create();
// 添加操作
dispatch_group_async(group, concurrentQueue, ^{printf("處理1\n");});
dispatch_group_async(group, concurrentQueue, ^{printf("處理2\n");});
dispatch_group_async(group, concurrentQueue, ^{printf("處理3\n");});
// 操作完成執(zhí)行
dispatch_group_notify(group, serialQueue, ^{printf("處理全部完成\n");});
執(zhí)行結(jié)果如下:
處理1
處理3
處理2
處理全部完成
除了dispatch_group_notify函數(shù)可以判斷執(zhí)行完成外菇篡,我們還可以利用dispatch_group_wait函數(shù)來進(jìn)行判斷漩符。
將以上代碼轉(zhuǎn)換為dispatch_group_wait函數(shù)形式編寫:
// 創(chuàng)建一個(gè)Serial Queue,用于執(zhí)行完成處理
dispatch_queue_t serialQueue = dispatch_queue_create("com.Sky.serialTest", NULL);
// 創(chuàng)建默認(rèn)優(yōu)先級Concurrent Queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
// 創(chuàng)建Dispatch Group
dispatch_group_t group = dispatch_group_create();
// 添加操作
dispatch_group_async(group, concurrentQueue, ^{printf("處理1\n");});
dispatch_group_async(group, concurrentQueue, ^{printf("處理2\n");});
dispatch_group_async(group, concurrentQueue, ^{printf("處理3\n");});
// 操作完成執(zhí)行
long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (result == 0) {
dispatch_async(serialQueue, ^{printf("處理結(jié)束");});
}
else{
// 處理沒結(jié)束的程序段
}
執(zhí)行結(jié)果如下:
處理1
處理3
處理2
處理全部完成
關(guān)于dispatch_group_wait函數(shù),有兩個(gè)參數(shù)和一個(gè)返回值:
- 第一個(gè)參數(shù)是dispatch_group_t類型驱还,我們需要傳遞需要進(jìn)行完成處理的Group嗜暴;
- 第二個(gè)參數(shù)為dispatch_time_t類型的變量。
- 這個(gè)函數(shù)的返回值為0代表Group中的執(zhí)行結(jié)束议蟆,不為0則代表指定時(shí)間到了闷沥,但是Group中的執(zhí)行還沒有完成。所以我們只需要對返回值進(jìn)行判斷咐容,就能知道Group中的處理是否完成舆逃。
上面的例子中,我們將時(shí)間設(shè)置為永久等待戳粒,所以只有Group中的處理全部完成才會得到返回值路狮,所以返回值的值恒為0。
我們可以根據(jù)需要來設(shè)置由時(shí)間是否到達(dá)還是處理是否全部完成來執(zhí)行收尾工作蔚约。相比較dispatch_group_notify而言奄妨,dispatch_group_wait函數(shù)更靈活一些。但在一般情況下苹祟,還是推薦dispatch_group_notify函數(shù)追加結(jié)果處理砸抛,從而簡化源代碼。
3.2.7 dispatch_barrier_async
當(dāng)我們在多個(gè)讀取中遇到了一個(gè)寫入操作時(shí)苔咪,可以使用dispatch_barrier_async函數(shù)锰悼。
在使用dispatch_barrier_async函數(shù)的時(shí)候,它會等待當(dāng)前Concurrent Dispatch Queue中的處理執(zhí)行結(jié)束后团赏,再將該處理追加到Concurrent Dispatch Queue箕般,也就是
dispatch_barrier_async函數(shù)所攜帶的處理單獨(dú)占用Concurrent Dispatch Queue。在執(zhí)行dispatch_barrier_async函數(shù)的時(shí)候舔清,它會屏蔽外界追加到Concurrent Dispatch Queue的操作丝里。當(dāng)它的處理執(zhí)行完成后曲初,Concurrent Dispatch Queue才開始繼續(xù)正常添加操作。
3.2.8 dispatch_sync
-
async意味著非同步杯聚,意思就是處理各自管各自的執(zhí)行臼婆;
-
而與之相反sync意味著同步,需要一個(gè)接一個(gè)的執(zhí)行幌绍。
??:
// 創(chuàng)建一個(gè)Concurrent dispatch Queue
dispatch_queue_t concurrentdispatchQueue = dispatch_queue_create("com.Sky.serialTest", DISPATCH_QUEUE_CONCURRENT);
// Serial dispatch Queue
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"1");});
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"2");});
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"3");});
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"4");});
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"5");});
dispatch_sync(concurrentdispatchQueue, ^{NSLog(@"6");});
執(zhí)行結(jié)果如下:
1
2
3
4
5
6
3.2.9 dispatch_apply
dispatch_apply函數(shù)是和Dispatch Group關(guān)聯(lián)的API颁褂,它的作用是按指定的次數(shù)將制定的Block追到制定的Dispatch Queue中,并等待全部處理執(zhí)行結(jié)束傀广。
dispatch_apply函數(shù)有3個(gè)參數(shù):
- 第一個(gè)為重復(fù)次數(shù)
- 第二個(gè)參數(shù)為追加對象的Dispatch Queue
- 第三個(gè)參數(shù)的Block為帶有參數(shù)的Blcok颁独,其參數(shù)相當(dāng)于for循環(huán)中的i的作用。
可用dispatch_apply來實(shí)現(xiàn)for循環(huán)的功能伪冰。
??:
// 創(chuàng)建一個(gè)Concurrent dispatch Queue
dispatch_queue_t concurrentdispatchQueue = dispatch_queue_create("com.Sky.serialTest", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, concurrentdispatchQueue, ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"完成");
執(zhí)行結(jié)果如下:
3
1
2
4
5
6
7
8
9
0
完成
3.2.10 dispatch_suspend/dispatch_resume
-
dispatch_suspend函數(shù)用來掛起隊(duì)列誓酒,隊(duì)列掛起后,追加到隊(duì)列中但還沒有開始執(zhí)行的處理贮聂,將暫停執(zhí)行靠柑。
dispatch_suspend(Queue)// 掛起隊(duì)列
-
dispatch_resume函數(shù)用來恢復(fù)隊(duì)列,恢復(fù)隊(duì)列后吓懈,暫停的處理繼續(xù)執(zhí)行歼冰。
dispatch_resume(Queue)// 暫停隊(duì)列
注意: dispatch_set_target_queue函數(shù)中,目標(biāo)隊(duì)列如果被掛起骄瓣,那么被轉(zhuǎn)換的隊(duì)列也會相同的掛起停巷。但是被轉(zhuǎn)換的隊(duì)列被掛起,目標(biāo)隊(duì)列則不受影響榕栏!
3.2.11 Dispatch Semaphore
Dispatch Semaphore和操作系統(tǒng)原理中的信號量一樣畔勤,都是用來避免數(shù)據(jù)競爭這一類問題的。
Dispatch Semaphore函數(shù)對數(shù)據(jù)競爭處理的對象的粒度更細(xì)扒磁。
Dispatch Semaphore是持有計(jì)數(shù)的信號庆揪,當(dāng)它的計(jì)數(shù)大于1時(shí),就會對其進(jìn)行減1并執(zhí)行處理妨托;當(dāng)計(jì)數(shù)為0或小于0的時(shí)候缸榛,就會等待下去。更加詳細(xì)的請自行參閱:《操作系統(tǒng)原理》兰伤。
??:
// 創(chuàng)建一個(gè)Concurrent dispatch Queue
dispatch_queue_t concurrentdispatchQueue = dispatch_queue_create("com.Sky.serialTest", DISPATCH_QUEUE_CONCURRENT);
// 提供一個(gè)信號量内颗,也就意味著同時(shí)只有1個(gè)線程能對資源進(jìn)行操作
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i =0; i<1000; ++i) {
dispatch_async(concurrentdispatchQueue, ^{
// 對信號量資源進(jìn)行判斷,當(dāng)信號量大于等于1的時(shí)候敦腔,將信號量減一(相當(dāng)于消耗一個(gè)資源)均澳,并返回,設(shè)置時(shí)間為永遠(yuǎn)等待
// 如果此函數(shù)沒有返回,則阻塞在這里找前,等待資源糟袁。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:[NSNumber numberWithInt:i]];
// 處理完成,信號量加一(相當(dāng)于釋放一個(gè)資源)
dispatch_semaphore_signal(semaphore);
});
}
3.2.12 dispatch_once
dispatch_once函數(shù)是保證在應(yīng)用程序執(zhí)行中只執(zhí)行一次指定處理的API躺盛。通常在建立單例中使用项戴。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 初始化單例
});
##3.2.13 Dispatch I/O
在讀取較大文件的時(shí)候?qū)⑽募懈铋_來,Dispatch I/O使用Global Dispatch Queue來同步讀取,加快讀取速度槽惫。利用dispatch_io_set_low_water
來設(shè)置一次讀取的大小周叮,dispatch_io_read
函數(shù)使用Global Dispatch Queue來并列讀取,當(dāng)讀取結(jié)束后會回調(diào)Block中的代碼進(jìn)行相關(guān)資源合并界斜。
######蘋果中使用Dispatch I/O 和 Dispatch Data的例子则吟。摘自[Apple System Log API里的源代碼](http://opensource.apple.com/source/Libc/Libc-763.11/gen/asl.c)
// 創(chuàng)建串行隊(duì)列
pipe_q = dispatch_queue_create("PipeQ", NULL);
// 創(chuàng)建 Dispatch I/O
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
close(fd);
});
*out_fd = fdpair[1];
// 該函數(shù)設(shè)定一次讀取的大小(分割大谐濉)
dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
//
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
if (err == 0) // err等于0 說明讀取無誤
{
// 讀取完“單個(gè)文件塊”的大小
size_t len = dispatch_data_get_size(pipedata);
if (len > 0)
{
// 定義一個(gè)字節(jié)數(shù)組bytes
const charchar *bytes = NULL;
charchar *encoded;
dispatch_data_t md = dispatch_data_create_map(pipedata, (const voidvoid **)&bytes, &len);
encoded = asl_core_encode_buffer(bytes, len);
asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
free(encoded);
_asl_send_message(NULL, merged_msg, -1, NULL);
asl_msg_release(merged_msg);
dispatch_release(md);
}
}
/*
每當(dāng)各個(gè)分割的文件塊讀取結(jié)束時(shí),將含有文件塊數(shù)據(jù)的 Dispatch Data(這里指pipedata) 傳遞給 dispatch_io_read 函數(shù)指定的讀取結(jié)束時(shí)回調(diào)用的block水慨,這個(gè)block拿到每一塊讀取好的Dispatch Data(這里指pipe data)得糜,然后進(jìn)行合并處理。
*/
if (done)
{
dispatch_semaphore_signal(sem);
dispatch_release(pipe_channel);
dispatch_release(pipe_q);
}
}
#3.3 GCD 實(shí)現(xiàn)
##3.3.1 Dispatch Queue
> ######GCD 的Dispatch Queue提供系統(tǒng)級線程管理晰洒,提高執(zhí)行效率朝抖。
GCD有2個(gè)執(zhí)行任務(wù)的函數(shù):
- 同步:dispatch_sync
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- 異步:dispatch_async
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
> GCD同步和異步的區(qū)別
> - 同步:只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力谍珊;
> - 異步:可以在新的線程中執(zhí)行任務(wù)治宣,具備可啟新線程的能力;
> ######注意: [具備開啟線程的能力砌滞,但不代表一定會開啟線程N暄!贝润!]
GCD有2類隊(duì)列:
- 并發(fā)隊(duì)列:多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
- 串行隊(duì)列:多個(gè)任務(wù)一個(gè)接著一個(gè)地執(zhí)行
#####GCD的實(shí)現(xiàn)方式:
- 異步+并發(fā) 開啟新線程 多個(gè)任務(wù)同時(shí)執(zhí)行
// 創(chuàng)建默認(rèn)優(yōu)先級Concurrent Queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(concurrentQueue, ^{
printf("處理1\n");
});
dispatch_async(concurrentQueue, ^{
printf("處理2\n");
});
dispatch_async(concurrentQueue, ^{
printf("處理3\n");
});
執(zhí)行結(jié)果如下:
處理 1
處理 3
處理 2
- 異步+串行 開啟新線程 任務(wù)依次執(zhí)行
// 創(chuàng)建一個(gè)Serial Queue,用于執(zhí)行完成處理
dispatch_queue_t serialQueue = dispatch_queue_create("com.Sky.serialTest", NULL);
dispatch_async(serialQueue, ^{
printf("處理1\n");
});
dispatch_async(serialQueue, ^{
printf("處理2\n");
});
dispatch_async(serialQueue, ^{
printf("處理3\n");
});
執(zhí)行結(jié)果如下:
處理 1
處理 2
處理 3
- 同步+并發(fā) 不開啟新線程 任務(wù)依次執(zhí)行
// 創(chuàng)建默認(rèn)優(yōu)先級Concurrent Queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_sync(concurrentQueue, ^{
printf("處理1\n");
});
dispatch_sync(concurrentQueue, ^{
printf("處理2\n");
});
dispatch_sync(concurrentQueue, ^{
printf("處理3\n");
});
執(zhí)行結(jié)果如下:
處理 1
處理 2
處理 3
- 同步+串行 不開新線程 任務(wù)依次執(zhí)行
// 創(chuàng)建一個(gè)Serial Queue,用于執(zhí)行完成處理
dispatch_queue_t serialQueue = dispatch_queue_create("com.Sky.serialTest", NULL);
dispatch_async(serialQueue, ^{
printf("處理1\n");
});
dispatch_async(serialQueue, ^{
printf("處理2\n");
});
dispatch_async(serialQueue, ^{
printf("處理3\n");
});
執(zhí)行結(jié)果如下:
處理 1
處理 2
處理 3
> ######注意:同步+主隊(duì)列绊茧,容易造成死循環(huán)
##3.3.1 Dispatch Scource
> Dispatch Source是GCD中的一個(gè)基本類型,從字面意思可稱為調(diào)度源打掘,它的作用是當(dāng)有一些特定的較底層的系統(tǒng)事件發(fā)生時(shí)华畏,調(diào)度源會捕捉到這些事件,然后可以做其他的邏輯處理尊蚁,調(diào)度源有多種類型亡笑,分別監(jiān)聽對應(yīng)類型的系統(tǒng)事件。
######Dispatch Source都有哪些類型:
- Timer Dispatch Source:定時(shí)調(diào)度源横朋。
- Signal Dispatch Source:監(jiān)聽UNIX信號調(diào)度源仑乌,比如監(jiān)聽代表掛起指令的SIGSTOP信號。
- Descriptor Dispatch Source:監(jiān)聽文件相關(guān)操作和Socket相關(guān)操作的調(diào)度源。
- Process Dispatch Source:監(jiān)聽進(jìn)程相關(guān)狀態(tài)的調(diào)度源绝骚。
- Mach port Dispatch Source:監(jiān)聽Mach相關(guān)事件的調(diào)度源耐版。
- Custom Dispatch Source:監(jiān)聽自定義事件的調(diào)度源。
######Dispatch Source可處理的所有事件:
- ![圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》](http://upload-images.jianshu.io/upload_images/3239766-d7aaa9082f4d3fbb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
> 注意:
> - DISPATCH_SOURCE_TYPE_DATA_ADD
當(dāng)同一時(shí)間压汪,一個(gè)事件的的觸發(fā)頻率很高粪牲,那么Dispatch Source會將這些響應(yīng)以ADD的方式進(jìn)行累積,然后等系統(tǒng)空閑時(shí)最終處理止剖,如果觸發(fā)頻率比較零散腺阳,那么Dispatch Source會將這些事件分別響應(yīng)。
> - DISPATCH_SOURCE_TYPE_DATA_OR 和上面的一樣穿香,是自定義的事件亭引,但是它是以O(shè)R的方式進(jìn)行累積
dispatch_source_t source = dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue)
######參數(shù):
- ![圖片來自:《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》](http://upload-images.jianshu.io/upload_images/3239766-2010e2343ca1d79e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
??:
//倒計(jì)時(shí)時(shí)間
__block int timeout = 3;
//創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//創(chuàng)建timer
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//設(shè)置1s觸發(fā)一次,0s的誤差
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //每秒執(zhí)行
//觸發(fā)的事件
dispatch_source_set_event_handler(_timer, ^{
if(timeout<=0){ //倒計(jì)時(shí)結(jié)束皮获,關(guān)閉
//取消dispatch源
dispatch_source_cancel(_timer);
}
else{
timeout--;
dispatch_async(dispatch_get_main_queue(), ^{
//更新主界面的操作
NSLog(@"~~~~~~~~~~~~~~~~%d", timeout);
});
}
});
//開始執(zhí)行dispatch源
dispatch_resume(_timer);
#總結(jié)
######GCD是系統(tǒng)級線程管理焙蚓,使用GCD可以提高執(zhí)行效率。
######GCD兩種隊(duì)列, 一種是串行隊(duì)列(Serial Dispatch Queue), 一種是并行隊(duì)列(Concurrent Dispatch Queue)洒宝」汗可通過dispatch_queue_create函數(shù)創(chuàng)建。
######GCD還提供Main Dispatch Queue(主線程中執(zhí)行的Dispatch Queue)與Global Dispatch Queue(全局的Concurrent Dispatch Queue)兩種的隊(duì)列雁歌。
> ######GCD的實(shí)現(xiàn)方式:
1. 異步+串行 開啟新線程 任務(wù)依次執(zhí)行
2. 異步+并發(fā) 開啟新線程 多個(gè)任務(wù)同時(shí)執(zhí)行
3. 異步+串行 開啟新線程 任務(wù)依次執(zhí)行
4. 同步+并發(fā) 不開啟新線程 任務(wù)依次執(zhí)行