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