一、簡介在iOS所有實現(xiàn)多線程的方案中,GCD應(yīng)該是最有魅力的,因為GCD本身是蘋果公司為多核的并行運算提出的解決方案衙传。GCD在工作時會自動利用更多的處理器核心,以充分利用更強大的機器厕九。GCD是Grand Central Dispatch的簡稱蓖捶,它是基于C語言的。如果使用GCD扁远,完全由系統(tǒng)管理線程俊鱼,我們不需要編寫線程代碼。只需定義想要執(zhí)行的任務(wù),然后添加到適當?shù)恼{(diào)度隊列(dispatch queue)畅买。GCD會負責創(chuàng)建線程和調(diào)度你的任務(wù)并闲,系統(tǒng)直接提供線程管理二、調(diào)度隊列(dispath queue)1.GCD的一個重要概念是隊列谷羞,它的核心理念:將長期運行的任務(wù)拆分成多個工作單元帝火,并將這些單元添加到dispath queue中,系統(tǒng)會為我們管理這些dispath queue湃缎,為我們在多個線程上執(zhí)行工作單元犀填,我們不需要直接啟動和管理后臺線程。2.系統(tǒng)提供了許多預(yù)定義的dispath queue嗓违,包括可以保證始終在主線程上執(zhí)行工作的dispath queue九巡。也可以創(chuàng)建自己的dispath queue,而且可以創(chuàng)建任意多個蹂季。GCD的dispath queue嚴格遵循FIFO(先進先出)原則冕广,添加到dispath queue的工作單元將始終按照加入dispath queue的順序啟動。3.dispatch queue按先進先出的順序,串行或并發(fā)地執(zhí)行任務(wù)1> serial dispatch queue一次只能執(zhí)行一個任務(wù), 當前任務(wù)完成才開始出列并啟動下一個任務(wù)2> concurrent dispatch queue則盡可能多地啟動任務(wù)并發(fā)執(zhí)行三偿洁、創(chuàng)建和管理dispatch queue1.獲得全局并發(fā)Dispatch Queue (concurrent dispatch queue)1> 并發(fā)dispatch queue可以同時并行地執(zhí)行多個任務(wù),不過并發(fā)queue仍然按先進先出的順序來啟動任務(wù)佳窑。并發(fā)queue會在之前的任務(wù)完成之前就出列下一個任務(wù)并開始執(zhí)行。并發(fā)queue同時執(zhí)行的任務(wù)數(shù)量會根據(jù)應(yīng)用和系統(tǒng)動態(tài)變化,各種因素包括:可用核數(shù)量父能、其它進程正在執(zhí)行的工作數(shù)量、其它串行dispatch queue中優(yōu)先任務(wù)的數(shù)量等.2> 系統(tǒng)給每個應(yīng)用提供三個并發(fā)dispatch queue,整個應(yīng)用內(nèi)全局共享,三個queue的區(qū)別是優(yōu)先級净神。你不需要顯式地創(chuàng)建這些queue,使用dispatch_get_global_queue函數(shù)來獲取這三個queue:[java] view plain copy// 獲取默認優(yōu)先級的全局并發(fā)dispatch queue? dispatch_queue_t? queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);? 第一個參數(shù)用于指定優(yōu)先級何吝,分別使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW兩個常量來獲取高和低優(yōu)先級的兩個queue溉委;第二個參數(shù)目前未使用到,默認0即可3> 雖然dispatch queue是引用計數(shù)的對象,但你不需要retain和release全局并發(fā)queue爱榕。因為這些queue對應(yīng)用是全局的,retain和release調(diào)用會被忽略瓣喊。你也不需要存儲這三個queue的引用,每次都直接調(diào)用dispatch_get_global_queue獲得queue就行了。2.創(chuàng)建串行Dispatch Queue (serial dispatch queue)1> 應(yīng)用的任務(wù)需要按特定順序執(zhí)行時,就需要使用串行Dispatch Queue,串行queue每次只能執(zhí)行一個任務(wù)黔酥。你可以使用串行queue來替代鎖,保護共享資源 或可變的數(shù)據(jù)結(jié)構(gòu)藻三。和鎖不一樣的是,串行queue確保任務(wù)按可預(yù)測的順序執(zhí)行。而且只要你異步地提交任務(wù)到串行queue,就永遠不會產(chǎn)生死鎖2> 你必須顯式地創(chuàng)建和管理所有你使用的串行queue,應(yīng)用可以創(chuàng)建任意數(shù)量的串行queue,但不要為了同時執(zhí)行更多任務(wù)而創(chuàng)建更多的串行queue跪者。如果你需要并發(fā)地執(zhí)行大量任務(wù),應(yīng)該把任務(wù)提交到全局并發(fā)queue3> 利用dispatch_queue_create函數(shù)創(chuàng)建串行queue,兩個參數(shù)分別是queue名和一組queue屬性[java] view plain copydispatch_queue_t queue;? queue = dispatch_queue_create("cn.itcast.queue", NULL);? 3.運行時獲得公共QueueGCD提供了函數(shù)讓應(yīng)用訪問幾個公共dispatch queue:1> 使用dispatch_get_current_queue函數(shù)作為調(diào)試用途,或者測試當前queue的標識棵帽。在block對象中調(diào)用這個函數(shù)會返回block提交到的queue(這個時候queue應(yīng)該正在執(zhí)行中)。在block對象之外調(diào)用這個函數(shù)會返回應(yīng)用的默認并發(fā)queue渣玲。2> 使用dispatch_get_main_queue函數(shù)獲得應(yīng)用主線程關(guān)聯(lián)的串行dispatch queue3> 使用dispatch_get_global_queue來獲得共享的并發(fā)queue4.Dispatch Queue的內(nèi)存管理1> Dispatch Queue和其它dispatch對象(還有dispatch source)都是引用計數(shù)的數(shù)據(jù)類型逗概。當你創(chuàng)建一個串行dispatch queue時,初始引用計數(shù)為 1,你可以使用dispatch_retain和dispatch_release函數(shù)來增加和減少引用計數(shù)。當引用計數(shù)到達 0 時,系統(tǒng)會異步地銷毀這個queue2> 對dispatch對象(如dispatch queue)retain和release 是很重要的,確保它們被使用時能夠保留在內(nèi)存中忘衍。和OC對象一樣,通用的規(guī)則是如果使用一個傳遞過來的queue,你應(yīng)該在使用前retain,使用完之后release3> 你不需要retain或release全局dispatch queue,包括全局并發(fā)dispatch queue和main dispatch queue4> 即使你實現(xiàn)的是自動垃圾收集的應(yīng)用,也需要retain和release創(chuàng)建的dispatch queue和其它dispatch對象逾苫。GCD 不支持垃圾收集模型來回收內(nèi)存四、添加任務(wù)到queue要執(zhí)行一個任務(wù),你需要將它添加到一個適當?shù)膁ispatch queue,你可以單個或按組來添加枚钓,也可以同步或異步地執(zhí)行一個任務(wù),也铅搓。一旦進入到queue,queue會負責盡快地執(zhí)行你的任務(wù)。一般可以用一個block來封裝任務(wù)內(nèi)容搀捷。1.添加單個任務(wù)到queue1> 異步添加任務(wù)你可以異步或同步地添加一個任務(wù)到Queue,盡可能地使用dispatch_async或dispatch_async_f函數(shù)異步地調(diào)度任務(wù)星掰。因為添加任務(wù)到Queue中時,無法確定這些代碼什么時候能夠執(zhí)行。因此異步地添加block或函數(shù),可以讓你立即調(diào)度這些代碼的執(zhí)行,然后調(diào)用線程可以繼續(xù)去做其它事情指煎。特別是應(yīng)用主線程一定要異步地 dispatch 任務(wù),這樣才能及時地響應(yīng)用戶事件2> 同步添加任務(wù)少數(shù)時候你可能希望同步地調(diào)度任務(wù),以避免競爭條件或其它同步錯誤蹋偏。 使用dispatch_sync和dispatch_sync_f函數(shù)同步地添加任務(wù)到Queue,這兩個函數(shù)會阻塞當前調(diào)用線程,直到相應(yīng)任務(wù)完成執(zhí)行。注意:絕對不要在任務(wù)中調(diào)用 dispatch_sync或dispatch_sync_f函數(shù),并同步調(diào)度新任務(wù)到當前正在執(zhí)行的 queue至壤。對于串行queue這一點特別重要,因為這樣做肯定會導致死鎖;而并發(fā)queue也應(yīng)該避免這樣做威始。3> 代碼演示[java] view plain copy// 調(diào)用前,查看下當前線程? NSLog(@"當前調(diào)用線程:%@", [NSThread currentThread]);? ? // 創(chuàng)建一個串行queue? dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", NULL);? ? dispatch_async(queue, ^{? ? ? NSLog(@"開啟了一個異步任務(wù)像街,當前線程:%@", [NSThread currentThread]);? });? ? dispatch_sync(queue, ^{? ? ? NSLog(@"開啟了一個同步任務(wù)黎棠,當前線程:%@", [NSThread currentThread]);? });? // 銷毀隊列? dispatch_release(queue);? 打印信息:[java] view plain copy2013-02-03 09:03:37.348 thread[6491:c07] 當前調(diào)用線程:{name = (null), num = 1}? 2013-02-03 09:03:37.349 thread[6491:1e03] 開啟了一個異步任務(wù),當前線程:{name = (null), num = 3}? 2013-02-03 09:03:37.350 thread[6491:c07] 開啟了一個同步任務(wù)镰绎,當前線程:{name = (null), num = 1}
2.并發(fā)地執(zhí)行循環(huán)迭代
如果你使用循環(huán)執(zhí)行固定次數(shù)的迭代, 并發(fā)dispatch queue可能會提高性能脓斩。
例如下面的for循環(huán):
[java] view plain copy
int i;
int count = 10;
for (i = 0; i < count; i++) {
printf("%d? ",i);
}
1> 如果每次迭代執(zhí)行的任務(wù)與其它迭代獨立無關(guān),而且循環(huán)迭代執(zhí)行順序也無關(guān)緊要的話,你可以調(diào)用dispatch_apply或dispatch_apply_f函數(shù)來替換循環(huán)。這兩個函數(shù)為每次循環(huán)迭代將指定的block或函數(shù)提交到queue畴栖。當dispatch到并發(fā) queue時,就有可能同時執(zhí)行多個循環(huán)迭代随静。用dispatch_apply或dispatch_apply_f時你可以指定串行或并發(fā) queue。并發(fā)queue允許同時執(zhí)行多個循環(huán)迭代,而串行queue就沒太大必要使用了。
下面代碼使用dispatch_apply替換了for循環(huán),你傳遞的block必須包含一個size_t類型的參數(shù),用來標識當前循環(huán)迭代燎猛。第一次迭代這個參數(shù)值為0,最后一次值為count - 1
[java] view plain copy
// 獲得全局并發(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);
});
// 銷毀隊列
dispatch_release(queue);
打印信息:
[java] view plain copy
1 2 0 3 4 5 6 7 8 9
可以看出恋捆,這些迭代是并發(fā)執(zhí)行的
和普通for循環(huán)一樣,dispatch_apply和dispatch_apply_f函數(shù)也是在所有迭代完成之后才會返回,因此這兩個函數(shù)會阻塞當前線程重绷,主線程中調(diào)用這兩個函數(shù)必須小心,可能會阻止事件處理循環(huán)并無法響應(yīng)用戶事件沸停。所以如果循環(huán)代碼需要一定的時間執(zhí)行,可以考慮在另一個線程中調(diào)用這兩個函數(shù)。如果你傳遞的參數(shù)是串行queue,而且正是執(zhí)行當前代碼的queue,就會產(chǎn)生死鎖昭卓。
3.在主線程中執(zhí)行任務(wù)
1> GCD提供一個特殊的dispatch queue,可以在應(yīng)用的主線程中執(zhí)行任務(wù)愤钾。只要應(yīng)用主線程設(shè)置了run loop(由CFRunLoopRef類型或NSRunLoop對象管理),就會自動創(chuàng)建這個queue,并且最后會自動銷毀。非Cocoa應(yīng)用如果不顯式地設(shè)置run loop, 就必須顯式地調(diào)用dispatch_main函數(shù)來顯式地激活這個dispatch queue候醒,否則雖然你可以添加任務(wù)到queue,但任務(wù)永遠不會被執(zhí)行能颁。
2> 調(diào)用dispatch_get_main_queue函數(shù)獲得應(yīng)用主線程的dispatch queue,添加到這個queue的任務(wù)由主線程串行化執(zhí)行
3> 代碼實現(xiàn),比如異步下載圖片后火焰,回到主線程顯示圖片
[java] view plain copy
// 異步下載圖片
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對象
GCD支持Cocoa內(nèi)存管理機制,因此可以在提交到queue的block中自由地使用Objective-C對象劲装。每個dispatch queue維護自己的autorelease pool確保釋放autorelease對象,但是queue不保證這些對象實際釋放的時間。如果應(yīng)用消耗大量內(nèi)存,并且創(chuàng)建大量autorelease對象,你需要創(chuàng)建自己的autorelease pool,用來及時地釋放不再使用的對象昌简。
五占业、暫停和繼續(xù)queue
我們可以使用dispatch_suspend函數(shù)暫停一個queue以阻止它執(zhí)行block對象;使用dispatch_resume函數(shù)繼續(xù)dispatch queue。調(diào)用dispatch_suspend會增加queue的引用計數(shù),調(diào)用dispatch_resume則減少queue的引用計數(shù)纯赎。當引用計數(shù)大于0時,queue就保持掛起狀態(tài)谦疾。因此你必須對應(yīng)地調(diào)用suspend和resume函數(shù)。掛起和繼續(xù)是異步的,而且只在執(zhí)行block之間(比如在執(zhí)行一個新的block之前或之后)生效犬金。掛起一個queue不會導致正在執(zhí)行的block停止念恍。
六、Dispatch Group的使用
假設(shè)有這樣一個需求:從網(wǎng)絡(luò)上下載兩張不同的圖片晚顷,然后顯示到不同的UIImageView上去峰伙,一般可以這樣實現(xiàn)
[java] view plain copy
// 根據(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;
});
});
}
雖然這種方案可以解決問題,但其實兩張圖片的下載過程并不需要按順序執(zhí)行该默,并發(fā)執(zhí)行它們可以提高執(zhí)行速度瞳氓。有個注意點就是必須等兩張圖片都下載完畢后才能回到主線程顯示圖片。Dispatch Group能夠在這種情況下幫我們提升性能栓袖。下面先看看Dispatch Group的用處:
我們可以使用dispatch_group_async函數(shù)將多個任務(wù)關(guān)聯(lián)到一個Dispatch Group和相應(yīng)的queue中匣摘,group會并發(fā)地同時執(zhí)行這些任務(wù)。而且Dispatch Group可以用來阻塞一個線程, 直到group關(guān)聯(lián)的所有的任務(wù)完成執(zhí)行裹刮。有時候你必須等待任務(wù)完成的結(jié)果,然后才能繼續(xù)后面的處理音榜。
下面用Dispatch Group優(yōu)化上面的代碼:
[java] view plain copy
// 根據(jù)url獲取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
// 這里并沒有自動釋放UIImage對象
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)建一個組
dispatch_group_t group = dispatch_group_create();
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
// 關(guān)聯(lián)一個任務(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)一個任務(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;
// 千萬不要在異步線程中自動釋放UIImage,因為當異步線程結(jié)束捧弃,異步線程的自動釋放池也會被銷毀赠叼,那么UIImage也會被銷毀
// 在這里釋放圖片資源
[image1 release];
[image2 release];
});
// 釋放group
dispatch_release(group);
});
}