一雕薪、簡(jiǎn)介
在iOS所有實(shí)現(xiàn)多線程的方案中辨嗽,GCD應(yīng)該是最有魅力的猜扮,因?yàn)镚CD本身是蘋果公司為多核的并行運(yùn)算提出的解決方案郎笆。GCD在工作時(shí)會(huì)自動(dòng)利用更多的處理器核心谭梗,以充分利用更強(qiáng)大的機(jī)器。GCD是Grand Central Dispatch的簡(jiǎn)稱宛蚓,它是基于C語(yǔ)言的激捏。如果使用GCD,完全由系統(tǒng)管理線程凄吏,我們不需要編寫線程代碼远舅。只需定義想要執(zhí)行的任務(wù),然后添加到適當(dāng)?shù)恼{(diào)度隊(duì)列(dispatch queue)。GCD會(huì)負(fù)責(zé)創(chuàng)建線程和調(diào)度你的任務(wù)痕钢,系統(tǒng)直接提供線程管理
二图柏、調(diào)度隊(duì)列(dispath queue)
- GCD的一個(gè)重要概念是隊(duì)列,它的核心理念:將長(zhǎng)期運(yùn)行的任務(wù)拆分成多個(gè)工作單元任连,并將這些單元添加到dispath queue中蚤吹,系統(tǒng)會(huì)為我們管理這些dispath queue,為我們?cè)诙鄠€(gè)線程上執(zhí)行工作單元随抠,我們不需要直接啟動(dòng)和管理后臺(tái)線程裁着。
- 系統(tǒng)提供了許多預(yù)定義的dispath queue,包括可以保證始終在主線程上執(zhí)行工作的dispath queue拱她。也可以創(chuàng)建自己的dispath queue二驰,而且可以創(chuàng)建任意多個(gè)。GCD的dispath queue嚴(yán)格遵循FIFO(先進(jìn)先出)原則秉沼,添加到dispath queue的工作單元將始終按照加入dispath queue的順序啟動(dòng)诸蚕。
- dispatch queue按先進(jìn)先出的順序,串行或并發(fā)地執(zhí)行任務(wù)
1> serial dispatch queue一次只能執(zhí)行一個(gè)任務(wù), 當(dāng)前任務(wù)完成才開始出列并啟動(dòng)下一個(gè)任務(wù)
2> concurrent dispatch queue則盡可能多地啟動(dòng)任務(wù)并發(fā)執(zhí)行
三、創(chuàng)建和管理dispatch queue
- 獲得全局并發(fā)Dispatch Queue (concurrent dispatch queue)
1> 并發(fā)dispatch queue可以同時(shí)并行地執(zhí)行多個(gè)任務(wù),不過并發(fā)queue仍然按先進(jìn)先出的順序來啟動(dòng)任務(wù)氧猬。并發(fā)queue會(huì)在之前的任務(wù)完成之前就出列下一個(gè)任務(wù)并開始執(zhí)行背犯。并發(fā)queue同時(shí)執(zhí)行的任務(wù)數(shù)量會(huì)根據(jù)應(yīng)用和系統(tǒng)動(dòng)態(tài)變化,各種因素包括:可用核數(shù)量、其它進(jìn)程正在執(zhí)行的工作數(shù)量盅抚、其它串行dispatch queue中優(yōu)先任務(wù)的數(shù)量等.
2> 系統(tǒng)給每個(gè)應(yīng)用提供三個(gè)并發(fā)dispatch queue,整個(gè)應(yīng)用內(nèi)全局共享,三個(gè)queue的區(qū)別是優(yōu)先級(jí)漠魏。你不需要顯式地創(chuàng)建這些queue,使用dispatch_get_global_queue函數(shù)來獲取這三個(gè)queue:
>// 獲取默認(rèn)優(yōu)先級(jí)的全局并發(fā)dispatch queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一個(gè)參數(shù)用于指定優(yōu)先級(jí),分別使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW兩個(gè)常量來獲取高和低優(yōu)先級(jí)的兩個(gè)queue妄均;第二個(gè)參數(shù)目前未使用到柱锹,默認(rèn)0即可
3> 雖然dispatch queue是引用計(jì)數(shù)的對(duì)象,但你不需要retain和release全局并發(fā)queue。因?yàn)檫@些queue對(duì)應(yīng)用是全局的,retain和release調(diào)用會(huì)被忽略丰包。你也不需要存儲(chǔ)這三個(gè)queue的引用,每次都直接調(diào)用dispatch_get_global_queue獲得queue就行了禁熏。
- 創(chuàng)建串行Dispatch Queue (serial dispatch queue)
1> 應(yīng)用的任務(wù)需要按特定順序執(zhí)行時(shí),就需要使用串行Dispatch Queue,串行queue每次只能執(zhí)行一個(gè)任務(wù)。你可以使用串行queue來替代鎖,保護(hù)共享資源 或可變的數(shù)據(jù)結(jié)構(gòu)邑彪。和鎖不一樣的是,串行queue確保任務(wù)按可預(yù)測(cè)的順序執(zhí)行瞧毙。而且只要你異步地提交任務(wù)到串行queue,就永遠(yuǎn)不會(huì)產(chǎn)生死鎖
2> 你必須顯式地創(chuàng)建和管理所有你使用的串行queue,應(yīng)用可以創(chuàng)建任意數(shù)量的串行queue,但不要為了同時(shí)執(zhí)行更多任務(wù)而創(chuàng)建更多的串行queue。如果你需要并發(fā)地執(zhí)行大量任務(wù),應(yīng)該把任務(wù)提交到全局并發(fā)queue
3> 利用dispatch_queue_create函數(shù)創(chuàng)建串行queue,兩個(gè)參數(shù)分別是queue名和一組queue屬性
dispatch_queue_t queue;
queue = dispatch_queue_create("cn.itcast.queue", NULL);
- 運(yùn)行時(shí)獲得公共QueueGCD提供了函數(shù)讓應(yīng)用訪問幾個(gè)公共dispatch queue:
1> 使用dispatch_get_current_queue函數(shù)作為調(diào)試用途,或者測(cè)試當(dāng)前queue的標(biāo)識(shí)。在block對(duì)象中調(diào)用這個(gè)函數(shù)會(huì)返回block提交到的queue(這個(gè)時(shí)候queue應(yīng)該正在執(zhí)行中)宙彪。在block對(duì)象之外調(diào)用這個(gè)函數(shù)會(huì)返回應(yīng)用的默認(rèn)并發(fā)queue矩动。2> 使用dispatch_get_main_queue函數(shù)獲得應(yīng)用主線程關(guān)聯(lián)的串行dispatch queue3> 使用dispatch_get_global_queue來獲得共享的并發(fā)queue
- Dispatch Queue的內(nèi)存管理
1> Dispatch Queue和其它dispatch對(duì)象(還有dispatch source)都是引用計(jì)數(shù)的數(shù)據(jù)類型。當(dāng)你創(chuàng)建一個(gè)串行dispatch queue時(shí),初始引用計(jì)數(shù)為 1,你可以使用dispatch_retain和dispatch_release函數(shù)來增加和減少引用計(jì)數(shù)释漆。當(dāng)引用計(jì)數(shù)到達(dá) 0 時(shí),系統(tǒng)會(huì)異步地銷毀這個(gè)queue
2> 對(duì)dispatch對(duì)象(如dispatch queue)retain和release 是很重要的,確保它們被使用時(shí)能夠保留在內(nèi)存中悲没。和OC對(duì)象一樣,通用的規(guī)則是如果使用一個(gè)傳遞過來的queue,你應(yīng)該在使用前retain,使用完之后release
3> 你不需要retain或release全局dispatch queue,包括全局并發(fā)dispatch queue和main dispatch queue
4> 即使你實(shí)現(xiàn)的是自動(dòng)垃圾收集的應(yīng)用,也需要retain和release創(chuàng)建的dispatch queue和其它dispatch對(duì)象。GCD 不支持垃圾收集模型來回收內(nèi)存
四男图、添加任務(wù)到queue
要執(zhí)行一個(gè)任務(wù),你需要將它添加到一個(gè)適當(dāng)?shù)膁ispatch queue,你可以單個(gè)或按組來添加示姿,也可以同步或異步地執(zhí)行一個(gè)任務(wù),也。一旦進(jìn)入到queue,queue會(huì)負(fù)責(zé)盡快地執(zhí)行你的任務(wù)逊笆。一般可以用一個(gè)block來封裝任務(wù)內(nèi)容栈戳。
- 添加單個(gè)任務(wù)到queue
1> 異步添加任務(wù)
你可以異步或同步地添加一個(gè)任務(wù)到Queue,盡可能地使用dispatch_async或dispatch_async_f函數(shù)異步地調(diào)度任務(wù)。因?yàn)樘砑尤蝿?wù)到Queue中時(shí),無(wú)法確定這些代碼什么時(shí)候能夠執(zhí)行览露。因此異步地添加block或函數(shù),可以讓你立即調(diào)度這些代碼的執(zhí)行,然后調(diào)用線程可以繼續(xù)去做其它事情。特別是應(yīng)用主線程一定要異步地 dispatch 任務(wù),這樣才能及時(shí)地響應(yīng)用戶事件
2> 同步添加任務(wù)
少數(shù)時(shí)候你可能希望同步地調(diào)度任務(wù),以避免競(jìng)爭(zhēng)條件或其它同步錯(cuò)誤譬胎。 使用dispatch_sync和dispatch_sync_f函數(shù)同步地添加任務(wù)到Queue,這兩個(gè)函數(shù)會(huì)阻塞當(dāng)前調(diào)用線程,直到相應(yīng)任務(wù)完成執(zhí)行差牛。注意:絕對(duì)不要在任務(wù)中調(diào)用 dispatch_sync或dispatch_sync_f函數(shù),并同步調(diào)度新任務(wù)到當(dāng)前正在執(zhí)行的 queue。對(duì)于串行queue這一點(diǎn)特別重要,因?yàn)檫@樣做肯定會(huì)導(dǎo)致死鎖;而并發(fā)queue也應(yīng)該避免這樣做堰乔。
3> 代碼演示
// 調(diào)用前偏化,查看下當(dāng)前線程
NSLog(@"當(dāng)前調(diào)用線程:%@", [NSThread currentThread]);
// 創(chuàng)建一個(gè)串行queue
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", NULL);
dispatch_async(queue, ^{
NSLog(@"開啟了一個(gè)異步任務(wù),當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"開啟了一個(gè)同步任務(wù)镐侯,當(dāng)前線程:%@", [NSThread currentThread]);
});
// 銷毀隊(duì)列
dispatch_release(queue);
打印信息:
2013-02-03 09:03:37.348 thread[6491:c07] 當(dāng)前調(diào)用線程:<NSThread: 0x714fa80>{name = (null), num = 1}
2013-02-03 09:03:37.349 thread[6491:1e03] 開啟了一個(gè)異步任務(wù)侦讨,當(dāng)前線程:<NSThread: 0x74520a0>{name = (null), num = 3}
2013-02-03 09:03:37.350 thread[6491:c07] 開啟了一個(gè)同步任務(wù),當(dāng)前線程:<NSThread: 0x714fa80>{name = (null), num = 1}
2.并發(fā)地執(zhí)行循環(huán)迭代
如果你使用循環(huán)執(zhí)行固定次數(shù)的迭代, 并發(fā)dispatch queue可能會(huì)提高性能苟翻。
例如下面的for循環(huán):
int i;
int count = 10;
for (i = 0; i < count; i++) {
printf("%d ",i);
}
1> 如果每次迭代執(zhí)行的任務(wù)與其它迭代獨(dú)立無(wú)關(guān),而且循環(huán)迭代執(zhí)行順序也無(wú)關(guān)緊要的話,你可以調(diào)用dispatch_apply或dispatch_apply_f函數(shù)來替換循環(huán)韵卤。這兩個(gè)函數(shù)為每次循環(huán)迭代將指定的block或函數(shù)提交到queue。當(dāng)dispatch到并發(fā) queue時(shí),就有可能同時(shí)執(zhí)行多個(gè)循環(huán)迭代崇猫。用dispatch_apply或dispatch_apply_f時(shí)你可以指定串行或并發(fā) queue沈条。并發(fā)queue允許同時(shí)執(zhí)行多個(gè)循環(huán)迭代,而串行queue就沒太大必要使用了。
下面代碼使用dispatch_apply替換了for循環(huán),你傳遞的block必須包含一個(gè)size_t類型的參數(shù),用來標(biāo)識(shí)當(dāng)前循環(huán)迭代诅炉。第一次迭代這個(gè)參數(shù)值為0,最后一次值為count - 1
// 獲得全局并發(fā)queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t count = 10;
dispatch_apply(count, queue, ^(size_t i) {
printf("%zd ", i);
});
// 銷毀隊(duì)列
dispatch_release(queue);
打印信息:
1 2 0 3 4 5 6 7 8 9
可以看出蜡歹,這些迭代是并發(fā)執(zhí)行的
和普通for循環(huán)一樣,dispatch_apply和dispatch_apply_f函數(shù)也是在所有迭代完成之后才會(huì)返回,因此這兩個(gè)函數(shù)會(huì)阻塞當(dāng)前線程涕烧,主線程中調(diào)用這兩個(gè)函數(shù)必須小心,可能會(huì)阻止事件處理循環(huán)并無(wú)法響應(yīng)用戶事件月而。所以如果循環(huán)代碼需要一定的時(shí)間執(zhí)行,可以考慮在另一個(gè)線程中調(diào)用這兩個(gè)函數(shù)。如果你傳遞的參數(shù)是串行queue,而且正是執(zhí)行當(dāng)前代碼的queue,就會(huì)產(chǎn)生死鎖议纯。
3.在主線程中執(zhí)行任務(wù)
1> GCD提供一個(gè)特殊的dispatch queue,可以在應(yīng)用的主線程中執(zhí)行任務(wù)父款。只要應(yīng)用主線程設(shè)置了run loop(由CFRunLoopRef類型或NSRunLoop對(duì)象管理),就會(huì)自動(dòng)創(chuàng)建這個(gè)queue,并且最后會(huì)自動(dòng)銷毀。非Cocoa應(yīng)用如果不顯式地設(shè)置run loop, 就必須顯式地調(diào)用dispatch_main函數(shù)來顯式地激活這個(gè)dispatch queue,否則雖然你可以添加任務(wù)到queue,但任務(wù)永遠(yuǎn)不會(huì)被執(zhí)行铛漓。
2> 調(diào)用dispatch_get_main_queue函數(shù)獲得應(yīng)用主線程的dispatch queue,添加到這個(gè)queue的任務(wù)由主線程串行化執(zhí)行
3> 代碼實(shí)現(xiàn)溯香,比如異步下載圖片后,回到主線程顯示圖片
// 異步下載圖片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// 回到主線程顯示圖片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
4.任務(wù)中使用Objective-C對(duì)象
GCD支持Cocoa內(nèi)存管理機(jī)制,因此可以在提交到queue的block中自由地使用Objective-C對(duì)象浓恶。每個(gè)dispatch queue維護(hù)自己的autorelease pool確保釋放autorelease對(duì)象,但是queue不保證這些對(duì)象實(shí)際釋放的時(shí)間玫坛。如果應(yīng)用消耗大量?jī)?nèi)存,并且創(chuàng)建大量autorelease對(duì)象,你需要?jiǎng)?chuàng)建自己的autorelease pool,用來及時(shí)地釋放不再使用的對(duì)象。
五包晰、暫停和繼續(xù)queue
我們可以使用dispatch_suspend函數(shù)暫停一個(gè)queue以阻止它執(zhí)行block對(duì)象;使用dispatch_resume函數(shù)繼續(xù)dispatch queue湿镀。調(diào)用dispatch_suspend會(huì)增加queue的引用計(jì)數(shù),調(diào)用dispatch_resume則減少queue的引用計(jì)數(shù)。當(dāng)引用計(jì)數(shù)大于0時(shí),queue就保持掛起狀態(tài)伐憾。因此你必須對(duì)應(yīng)地調(diào)用suspend和resume函數(shù)伊群。掛起和繼續(xù)是異步的,而且只在執(zhí)行block之間(比如在執(zhí)行一個(gè)新的block之前或之后)生效。掛起一個(gè)queue不會(huì)導(dǎo)致正在執(zhí)行的block停止潜支。
六娄徊、Dispatch Group的使用
假設(shè)有這樣一個(gè)需求:從網(wǎng)絡(luò)上下載兩張不同的圖片,然后顯示到不同的UIImageView上去胸嘴,一般可以這樣實(shí)現(xiàn)
// 根據(jù)url獲取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
return [UIImage imageWithData:data];
}
- (void)downloadImages {
// 異步下載圖片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下載第一張圖片
NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
UIImage *image1 = [self imageWithURLString:url1];
// 下載第二張圖片
NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
UIImage *image2 = [self imageWithURLString:url2];
// 回到主線程顯示圖片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView1.image = image1;
self.imageView2.image = image2;
});
});
}
雖然這種方案可以解決問題雏掠,但其實(shí)兩張圖片的下載過程并不需要按順序執(zhí)行,并發(fā)執(zhí)行它們可以提高執(zhí)行速度劣像。有個(gè)注意點(diǎn)就是必須等兩張圖片都下載完畢后才能回到主線程顯示圖片乡话。Dispatch Group能夠在這種情況下幫我們提升性能。下面先看看Dispatch Group的用處:
我們可以使用dispatch_group_async函數(shù)將多個(gè)任務(wù)關(guān)聯(lián)到一個(gè)Dispatch Group和相應(yīng)的queue中耳奕,group會(huì)并發(fā)地同時(shí)執(zhí)行這些任務(wù)绑青。而且Dispatch Group可以用來阻塞一個(gè)線程, 直到group關(guān)聯(lián)的所有的任務(wù)完成執(zhí)行。有時(shí)候你必須等待任務(wù)完成的結(jié)果,然后才能繼續(xù)后面的處理屋群。
下面用Dispatch Group優(yōu)化上面的代碼:
// 根據(jù)url獲取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
// 這里并沒有自動(dòng)釋放UIImage對(duì)象
return [[UIImage alloc] initWithData:data];
}
- (void)downloadImages {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 異步下載圖片
dispatch_async(queue, ^{
// 創(chuàng)建一個(gè)組
dispatch_group_t group = dispatch_group_create();
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
// 關(guān)聯(lián)一個(gè)任務(wù)到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下載第一張圖片
NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
image1 = [self imageWithURLString:url1];
});
// 關(guān)聯(lián)一個(gè)任務(wù)到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下載第一張圖片
NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
image2 = [self imageWithURLString:url2];
});
// 等待組中的任務(wù)執(zhí)行完畢,回到主線程執(zhí)行block回調(diào)
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
self.imageView1.image = image1;
self.imageView2.image = image2;
// 千萬(wàn)不要在異步線程中自動(dòng)釋放UIImage闸婴,因?yàn)楫?dāng)異步線程結(jié)束,異步線程的自動(dòng)釋放池也會(huì)被銷毀芍躏,那么UIImage也會(huì)被銷毀
// 在這里釋放圖片資源
[image1 release];
[image2 release];
});
// 釋放group
dispatch_release(group);
});
}
dispatch_group_notify函數(shù)用來指定一個(gè)額外的block掠拳,該block將在group中所有任務(wù)完成后執(zhí)行
GCD例子
// 后臺(tái)執(zhí)行:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// something
});
// 主線程執(zhí)行:
dispatch_async(dispatch_get_main_queue(), ^{
// something
});
// 一次性執(zhí)行:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
// 延遲2秒執(zhí)行:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// code to be executed on the main queue after delay
});
// 自定義dispatch_queue_t
dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);
dispatch_async(urls_queue, ^{
// your code
});
dispatch_release(urls_queue);
// 合并匯總結(jié)果
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
// 并行執(zhí)行的線程一
});
dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
// 并行執(zhí)行的線程二
});
dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
// 匯總結(jié)果
});
GCD的另一個(gè)用處是可以讓程序在后臺(tái)較長(zhǎng)久的運(yùn)行。
在沒有使用GCD時(shí)纸肉,當(dāng)app被按home鍵退出后溺欧,app僅有最多5秒鐘的時(shí)候做一些保存或清理資源的工作。但是在使用GCD后柏肪,app最多有10分鐘的時(shí)間在后臺(tái)長(zhǎng)久運(yùn)行姐刁。這個(gè)時(shí)間可以用來做清理本地緩存,發(fā)送統(tǒng)計(jì)數(shù)據(jù)等工作烦味。
讓程序在后臺(tái)長(zhǎng)久運(yùn)行的示例代碼如下:
// AppDelegate.h文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;
// AppDelegate.m文件
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self beingBackgroundUpdateTask];
// 在這里加上你需要長(zhǎng)久運(yùn)行的代碼
[self endBackgroundUpdateTask];
}
- (void)beingBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void)endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}