重新回顧、學(xué)習(xí)GCD、Block。先貼出一篇不錯(cuò)的講解GCD基礎(chǔ)使用的文章
原文地址:http://blog.csdn.net/aolan1108/article/details/17283415
做了2年的ios開發(fā),很想靜下心來想想,做一些總結(jié),但是苦于生活和工作方面的種種原因,一直沒能如愿。今天終于下定決心,把自己所學(xué)所想記錄下來,方便以后查看,同時(shí)可供大家分享。
//主線程異步執(zhí)行 -(void)action1{ dispatch_async(dispatch_get_main_queue(), ^{ for (int i = 0; i< 5; i++) NSLog(@"主線程異步執(zhí)行===========%d",i); }); NSLog(@"=================主線程異步執(zhí)行"); }
2013-12-09 14:36:20.863 TestGCD[872:a0b] =================主線程異步執(zhí)行
2013-12-09 14:36:20.864 TestGCD[872:a0b] 主線程異步執(zhí)行===========0
2013-12-09 14:36:20.865 TestGCD[872:a0b] 主線程異步執(zhí)行===========1
2013-12-09 14:36:20.865 TestGCD[872:a0b] 主線程異步執(zhí)行===========2
2013-12-09 14:36:20.865 TestGCD[872:a0b] 主線程異步執(zhí)行===========3
2013-12-09 14:36:20.866 TestGCD[872:a0b] 主線程異步執(zhí)行===========4
//主線程同步執(zhí)行 -(void)action2{ dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i< 3; i++) NSLog(@"并發(fā)線程異步執(zhí)行===========%d",i); dispatch_sync(dispatch_get_main_queue(), ^{ for (int i = 0; i< 3; i++) NSLog(@"主線程同步執(zhí)行===========%d",i); }); NSLog(@"===========主線程執(zhí)行完畢"); }); NSLog(@"===========并發(fā)線程可能正在執(zhí)行"); }
打印結(jié)果:
2013-12-09 15:22:22.352 TestGCD[1269:a0b] ===========并發(fā)線程可能正在執(zhí)行
2013-12-09 15:22:22.352 TestGCD[1269:1403] 并發(fā)線程異步執(zhí)行===========0
2013-12-09 15:22:22.355 TestGCD[1269:1403] 并發(fā)線程異步執(zhí)行===========1
2013-12-09 15:22:22.356 TestGCD[1269:1403] 并發(fā)線程異步執(zhí)行===========2
2013-12-09 15:22:22.357 TestGCD[1269:a0b] 主線程同步執(zhí)行===========0
2013-12-09 15:22:22.358 TestGCD[1269:a0b] 主線程同步執(zhí)行===========1
2013-12-09 15:22:22.358 TestGCD[1269:a0b] 主線程同步執(zhí)行===========2
2013-12-09 15:22:22.359 TestGCD[1269:1403] ===========主線程執(zhí)行完畢
從結(jié)果中我們仔細(xì)理解,就會(huì)比較深刻,首先我們看到打印語句4最先執(zhí)行(有可能不是最先執(zhí)行,這個(gè)是由系統(tǒng)決定的),說明我們提交到并發(fā)隊(duì)列dispatch_get_global_queue的執(zhí)行方式是異步的;其次打印語句2是在打印語句1執(zhí)行完才開始執(zhí)行,這說明dispatch_get_global_queue雖然是并發(fā)隊(duì)列,但是其內(nèi)部的任務(wù)執(zhí)行順序是串行的;最后,我們看到打印語句3是在打印語句2執(zhí)行完成后再執(zhí)行,說明主線程同步執(zhí)行是阻塞的,我們通常會(huì)將UI的刷新用同步方式放到主線程中去操作,當(dāng)然這種操作的時(shí)間一般都比較短,以至于用戶幾乎無法察覺。
例子三,其實(shí)GCD的操作真正的意義當(dāng)然不在于在主線程中做一些操作,在很多時(shí)候,我們都會(huì)用到后臺(tái)線程,如果你的iphone是多核的cpu,那么恭喜你,你可以有流暢的操作體驗(yàn),即使不是多核,你的操作體驗(yàn)也相對(duì)流暢,因?yàn)槟憧梢詫⑿枰罅繒r(shí)間才能執(zhí)行完成的任務(wù)放到后臺(tái)線程中去執(zhí)行,用戶就不會(huì)有死機(jī)感,譬如說圖片加載、網(wǎng)絡(luò)請(qǐng)求、復(fù)雜邏輯的數(shù)據(jù)解析等等。
-(void)action3{ dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i< 10000; i++) NSLog(@"=================%d",i); }); NSLog(@"=================后臺(tái)異步執(zhí)行"); }
你可以迅速點(diǎn)擊兩次按鈕,即連續(xù)執(zhí)行兩次action3方法,在這里,我們截取部分打印結(jié)果:
2013-12-09 15:51:30.864 TestGCD[1350:3007] =================0
2013-12-09 15:51:30.864 TestGCD[1350:a0b] =================后臺(tái)異步執(zhí)行
2013-12-09 15:51:30.864 TestGCD[1350:3007] =================1
2013-12-09 15:51:30.865 TestGCD[1350:3007] =================2
2013-12-09 15:51:30.865 TestGCD[1350:3007] =================3
......
2013-12-09 15:51:31.007 TestGCD[1350:3007] =================600
2013-12-09 15:51:31.007 TestGCD[1350:3007] =================601
2013-12-09 15:51:31.007 TestGCD[1350:a0b] =================后臺(tái)異步執(zhí)行
2013-12-09 15:51:31.007 TestGCD[1350:3007] =================602
2013-12-09 15:51:31.008 TestGCD[1350:3007] =================603
2013-12-09 15:51:31.007 TestGCD[1350:473] =================0
2013-12-09 15:51:31.008 TestGCD[1350:473] =================1
2013-12-09 15:51:31.008 TestGCD[1350:473] =================2
2013-12-09 15:51:31.008 TestGCD[1350:3007] =================604
2013-12-09 15:51:31.008 TestGCD[1350:473] =================3
2013-12-09 15:51:31.008 TestGCD[1350:3007] =================605
......
我們看到由于action3被執(zhí)行了兩次,在點(diǎn)擊第一次后,屏幕仍然接受點(diǎn)擊時(shí)間,說明主線程沒有被阻塞,用戶體驗(yàn)仍然很流暢。兩次點(diǎn)擊實(shí)際上是提交了兩個(gè)任務(wù)到dispatch_get_global_queue隊(duì)列上,第一次任務(wù)并沒有執(zhí)行完成,第二次任務(wù)就開始執(zhí)行,說明dispatch_get_global_queue是并行隊(duì)列,即任務(wù)塊與任務(wù)塊之間是并發(fā)執(zhí)行的。
例子四,肯定會(huì)有小伙伴們說后臺(tái)既然可以異步執(zhí)行,那么應(yīng)該也可以同步執(zhí)行嘍,是的,你是對(duì)的,但是在實(shí)際的開發(fā)過程中沒有什么意義,我們會(huì)發(fā)現(xiàn)改方法會(huì)產(chǎn)生阻塞,但是卻不能用來刷新UI,也沒有體現(xiàn)多核處理器的優(yōu)勢(shì)。
//后臺(tái)同步執(zhí)行 -(void)action4{ dispatch_sync(dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i< 10000; i++) NSLog(@"=================%d",i); }); NSLog(@"=================后臺(tái)同步執(zhí)行"); }
我們也是連續(xù)點(diǎn)擊兩次按鈕,執(zhí)行兩次action4方法,我們看到的打印結(jié)果讓我們很驚訝,可能會(huì)有人說,dispatch_get_global_queue隊(duì)列不是并發(fā)隊(duì)列嗎,怎么執(zhí)行的結(jié)果是這樣的。在這里我需要解釋下,由于提交給全局隊(duì)列的執(zhí)行方式是同步的,這里實(shí)際上是產(chǎn)生了阻塞,即必須是該任務(wù)完成后才能執(zhí)行下面的任務(wù),我們看到打印語句2執(zhí)行的位置就知道了。所以同步一般在刷新UI界面或者處理共享數(shù)據(jù)的時(shí)候使用,而且任務(wù)的處理時(shí)間不能太長(zhǎng),會(huì)影響用戶體驗(yàn)。
2013-12-09 16:04:35.967 TestGCD[1385:a0b] =================0
2013-12-09 16:04:35.969 TestGCD[1385:a0b] =================1
2013-12-09 16:04:35.969 TestGCD[1385:a0b] =================2
2013-12-09 16:04:40.645 TestGCD[1385:a0b] =================9997
2013-12-09 16:04:40.645 TestGCD[1385:a0b] =================9998
2013-12-09 16:04:40.646 TestGCD[1385:a0b] =================9999
2013-12-09 16:04:40.646 TestGCD[1385:a0b] =================后臺(tái)同步執(zhí)行
2013-12-09 16:04:40.647 TestGCD[1385:a0b] =================0
2013-12-09 16:04:40.647 TestGCD[1385:a0b] =================1
2013-12-09 16:04:40.648 TestGCD[1385:a0b] =================2
2013-12-09 16:04:40.648 TestGCD[1385:a0b] =================3
......
2013-12-09 16:04:45.218 TestGCD[1385:a0b] =================9998
2013-12-09 16:04:45.218 TestGCD[1385:a0b] =================9999
2013-12-09 16:04:45.218 TestGCD[1385:a0b] =================后臺(tái)同步執(zhí)行
例子五,開發(fā)人員可以自己定義一個(gè)隊(duì)列用于執(zhí)行后臺(tái)線程,同全局隊(duì)列使用方法基本類似,在非arc的情況下,需要開發(fā)人員手動(dòng)釋放。
//自定義dispatch_queue_t -(void)action5{ dispatch_queue_t urls_queue = dispatch_queue_create("myQueue", NULL); dispatch_async(urls_queue, ^{ for (int i = 0; i< 5; i++) NSLog(@"自定義dispatch_queue_t===========%d",i); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"同主線程交互==========="); }); }); dispatch_release(urls_queue); //arc不要 }
打印結(jié)果:
2013-12-09 16:43:52.416 TestGCD[1464:1417] 自定義dispatch_queue_t===========0
2013-12-09 16:43:52.417 TestGCD[1464:1417] 自定義dispatch_queue_t===========1
2013-12-09 16:43:52.418 TestGCD[1464:1417] 自定義dispatch_queue_t===========2
2013-12-09 16:43:52.419 TestGCD[1464:1417] 自定義dispatch_queue_t===========3
2013-12-09 16:43:52.420 TestGCD[1464:1417] 自定義dispatch_queue_t===========4
2013-12-09 16:43:52.420 TestGCD[1464:a0b] 同主線程交互===========
例子六,除此之外,調(diào)度方式還有延遲執(zhí)行。
//延遲3秒執(zhí)行 -(void)action6{ double delayInSeconds = 3.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ NSLog(@"延遲3秒執(zhí)行==========="); }); }
打印結(jié)果:
2013-12-09 16:48:58.473 TestGCD[1484:a0b] 延遲3秒執(zhí)行===========
例子七,一次性執(zhí)行,主要用于創(chuàng)建單例對(duì)象。在ios4.0以前,我們創(chuàng)建單例會(huì)用到互斥鎖@synchronized來確保其他線程沒有對(duì)self對(duì)象進(jìn)行修改,一般在共用變量的時(shí)候使用。
+ (NetworkManager *)getNetworkInstance{ @synchronized(self){ if (nil == network) network = [[NetworkManager alloc] init]; } return network; }
ios4.0之后,我們可以用GCD創(chuàng)建單例,下面的GCD語法只會(huì)執(zhí)行一次,代碼如下:
+(UserManager *)sharedManager{ static UserManager *_manager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _manager = [[UserManager alloc]init]; }); return _manager; }
該方法的好處有:一、線程安全;二、很好滿足靜態(tài)分析器的要求;三、兼容ARC;四、僅需要少量代碼。
例子八,合并匯總結(jié)果,dispatch_group_t隊(duì)列非常好用,當(dāng)我們有多個(gè)異步執(zhí)行的隊(duì)列在執(zhí)行,我們還有一個(gè)任務(wù)需要用到這多個(gè)異步執(zhí)行隊(duì)列執(zhí)行的結(jié)果時(shí),我們就會(huì)用到dispatch_group_t,廢話不多說,直接上代碼:
//合并匯總結(jié)果 -(void)action8{ dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i < 5; i++) NSLog(@"并行執(zhí)行的線程一=============%d",i); }); dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ for (int i = 0; i < 5; i++) NSLog(@"并行執(zhí)行的線程二=============%d",i); }); dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{ NSLog(@"匯總結(jié)果==========="); }); dispatch_release(group); }
2013-12-10 11:48:39.139 TestGCD[699:1403] 并行執(zhí)行的線程一=============0
2013-12-10 11:48:39.139 TestGCD[699:3807] 并行執(zhí)行的線程二=============0
2013-12-10 11:48:39.143 TestGCD[699:1403] 并行執(zhí)行的線程一=============1
2013-12-10 11:48:39.143 TestGCD[699:3807] 并行執(zhí)行的線程二=============1
2013-12-10 11:48:39.144 TestGCD[699:1403] 并行執(zhí)行的線程一=============2
2013-12-10 11:48:39.144 TestGCD[699:3807] 并行執(zhí)行的線程二=============2
2013-12-10 11:48:39.145 TestGCD[699:1403] 并行執(zhí)行的線程一=============3
2013-12-10 11:48:39.145 TestGCD[699:3807] 并行執(zhí)行的線程二=============3
2013-12-10 11:48:39.146 TestGCD[699:3807] 并行執(zhí)行的線程二=============4
2013-12-10 11:48:39.146 TestGCD[699:1403] 并行執(zhí)行的線程一=============4
2013-12-10 11:48:39.147 TestGCD[699:3807] 匯總結(jié)果===========
我們看到第三條打印語句是在前兩條打印語句執(zhí)行之后才執(zhí)行。
這也是我綜合了官方文檔和網(wǎng)上的很多資料總結(jié)出來的,并且用實(shí)際的代碼調(diào)試出來的結(jié)果,希望能給大家一些幫助,對(duì)于其中的不足,歡迎大家給出不同意見。
參考:http://www.companysz.com/pure/archive/2013/03/31/2977420.html
2014_07_16今天再次翻看GCD。再瀏覽過程中,發(fā)現(xiàn)了另外一篇不錯(cuò)的講解隊(duì)列的帖子,現(xiàn)轉(zhuǎn)發(fā)之,希望能對(duì)你有所幫助和啟發(fā),里面提到死鎖的問題,可以思考和交流。
原文地址:http://www.companysz.com/sunfrog/p/3305614.html
GCD編程的核心就是dispatch隊(duì)列,dispatch block的執(zhí)行最終都會(huì)放進(jìn)某個(gè)隊(duì)列中去進(jìn)行,它類似NSOperationQueue但更復(fù)雜也更強(qiáng)大,并且可以嵌套使用。所以說,結(jié)合block實(shí)現(xiàn)的GCD,把函數(shù)閉包(Closure)的特性發(fā)揮得淋漓盡致。
dispatch隊(duì)列的生成可以有這幾種方式:
1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL); //生成一個(gè)串行隊(duì)列,隊(duì)列中的block按照先進(jìn)先出(FIFO)的順序去執(zhí)行,實(shí)際上為單線程執(zhí)行。第一個(gè)參數(shù)是隊(duì)列的名稱,在調(diào)試程序時(shí)會(huì)非常有用,所有盡量不要重名了。
2. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT); //生成一個(gè)并發(fā)執(zhí)行隊(duì)列,block被分發(fā)到多個(gè)線程去執(zhí)行
3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //獲得程序進(jìn)程缺省產(chǎn)生的并發(fā)隊(duì)列,可設(shè)定優(yōu)先級(jí)來選擇高、中、低三個(gè)優(yōu)先級(jí)隊(duì)列。由于是系統(tǒng)默認(rèn)生成的,所以無法調(diào)用dispatch_resume()和dispatch_suspend()來控制執(zhí)行繼續(xù)或中斷。需要注意的是,三個(gè)隊(duì)列不代表三個(gè)線程,可能會(huì)有更多的線程。并發(fā)隊(duì)列可以根據(jù)實(shí)際情況來自動(dòng)產(chǎn)生合理的線程數(shù),也可理解為dispatch隊(duì)列實(shí)現(xiàn)了一個(gè)線程池的管理,對(duì)于程序邏輯是透明的。
官網(wǎng)文檔解釋說共有三個(gè)并發(fā)隊(duì)列,但實(shí)際還有一個(gè)更低優(yōu)先級(jí)的隊(duì)列,設(shè)置優(yōu)先級(jí)為DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode調(diào)試時(shí)可以觀察到正在使用的各個(gè)dispatch隊(duì)列。
4. dispatch_queue_t queue = dispatch_get_main_queue(); //獲得主線程的dispatch隊(duì)列,實(shí)際是一個(gè)串行隊(duì)列。同樣無法控制主線程dispatch隊(duì)列的執(zhí)行繼續(xù)或中斷。
接下來我們可以使用dispatch_async或dispatch_sync函數(shù)來加載需要運(yùn)行的block。
dispatch_async(queue, ^{
//block具體代碼
}); //異步執(zhí)行block,函數(shù)立即返回
dispatch_sync(queue, ^{
//block具體代碼
}); //同步執(zhí)行block,函數(shù)不返回,一直等到block執(zhí)行完畢。編譯器會(huì)根據(jù)實(shí)際情況優(yōu)化代碼,所以有時(shí)候你會(huì)發(fā)現(xiàn)block其實(shí)還在當(dāng)前線程上執(zhí)行,并沒用產(chǎn)生新線程。
實(shí)際編程經(jīng)驗(yàn)告訴我們,盡可能避免使用dispatch_sync,嵌套使用時(shí)還容易引起程序死鎖。
如果queue1是一個(gè)串行隊(duì)列的話,這段代碼立即產(chǎn)生死鎖:
dispatch_sync(queue1, ^{
dispatch_sync(queue1, ^{
......
});
......
});
不妨思考下,為什么下面代碼在主線程中執(zhí)行會(huì)死鎖:
dispatch_sync(dispatch_get_main_queue(), ^{
......
});
那實(shí)際運(yùn)用中,一般可以用dispatch這樣來寫,常見的網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)多線程執(zhí)行模型:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//子線程中開始網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)
//更新數(shù)據(jù)模型
dispatch_sync(dispatch_get_main_queue(), ^{
//在主線程中更新UI代碼
});
});
程序的后臺(tái)運(yùn)行和UI更新代碼緊湊,代碼邏輯一目了然。
dispatch隊(duì)列是線程安全的,可以利用串行隊(duì)列實(shí)現(xiàn)鎖的功能。比如多線程寫同一數(shù)據(jù)庫,需要保持寫入的順序和每次寫入的完整性,簡(jiǎn)單地利用串行隊(duì)列即可實(shí)現(xiàn):
dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.writedb", DISPATCH_QUEUE_SERIAL);
- (void)writeDB:(NSData *)data
{
dispatch_async(queue1, ^{
//write database
});
}
下一次調(diào)用writeDB:必須等到上次調(diào)用完成后才能進(jìn)行,保證writeDB:方法是線程安全的。
dispatch隊(duì)列還實(shí)現(xiàn)其它一些常用函數(shù),包括:
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重復(fù)執(zhí)行block,需要注意的是這個(gè)方法是同步返回,也就是說等到所有block執(zhí)行完畢才返回,如需異步返回則嵌套在dispatch_async中來使用。多個(gè)block的運(yùn)行是否并發(fā)或串行執(zhí)行也依賴queue的是否并發(fā)或串行。
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //這個(gè)函數(shù)可以設(shè)置同步執(zhí)行的block,它會(huì)等到在它加入隊(duì)列之前的block執(zhí)行完畢后,才開始執(zhí)行。在它之后加入隊(duì)列的block,則等到這個(gè)block執(zhí)行完畢后才開始執(zhí)行。
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函數(shù)
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //延遲執(zhí)行block
最后再來看看dispatch隊(duì)列的一個(gè)很有特色的函數(shù):
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
它會(huì)把需要執(zhí)行的任務(wù)對(duì)象指定到不同的隊(duì)列中去處理,這個(gè)任務(wù)對(duì)象可以是dispatch隊(duì)列,也可以是dispatch源(以后博文會(huì)介紹)。而且這個(gè)過程可以是動(dòng)態(tài)的,可以實(shí)現(xiàn)隊(duì)列的動(dòng)態(tài)調(diào)度管理等等。比如說有兩個(gè)隊(duì)列dispatchA和dispatchB,這時(shí)把dispatchA指派到dispatchB:
dispatch_set_target_queue(dispatchA, dispatchB);
那么dispatchA上還未運(yùn)行的block會(huì)在dispatchB上運(yùn)行。這時(shí)如果暫停dispatchA運(yùn)行:
dispatch_suspend(dispatchA);
則只會(huì)暫停dispatchA上原來的block的執(zhí)行,dispatchB的block則不受影響。而如果暫停dispatchB的運(yùn)行,則會(huì)暫停dispatchA的運(yùn)行。
這里只簡(jiǎn)單舉個(gè)例子,說明dispatch隊(duì)列運(yùn)行的靈活性,在實(shí)際應(yīng)用中你會(huì)逐步發(fā)掘出它的潛力。
dispatch隊(duì)列不支持cancel(取消),沒有實(shí)現(xiàn)dispatch_cancel()函數(shù),不像NSOperationQueue,不得不說這是個(gè)小小的缺憾。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注