關於GCD 的全解(轉載自http://blog.csdn.net/wangqiuyun/article/details/19198467)

從 別人哪裏轉載的,寫的很好


一、相關概念

    GCD全稱Grand Central Dispatch,是Apple提供的一套低層API,提供了一種新的方法來進行併發程序編寫。GCD有點像NSOperationQueue,但它比NSOpertionQueue更底層更高效,並且它不是Cocoa框架的一部分。GCD的API很大程度上基於block,當然,GCD也可以脫離block來使用,比如使用傳統c機制提供函數指針和上下文指針。實踐證明,當配合block使用時,GCD非常簡單易用且能發揮其最大能力。

二、初步使用

    1、隊列:GCD使程序員可以不直接和線程打交道,而是通過把任務分配給dispatch queues,然後讓這些queues去執行task,其中涉及到的三種隊列如下:

    1)、main queue:main_queue 與主線程功能相同,提交至main queue的任務會在主線程中執行,這是一個串行隊列,使用dispatch_get_main_queue()獲取;

    2)、Global queues:全局隊列是併發隊列,並由整個進程共享,進程中存在三個全局隊列:高、中(默認)、低三個優先級隊列,使用dispatch_get_global_queue()獲取;

    3)、用戶隊列:GCD並不這樣稱呼這種隊列, 但是沒有一個特定的名字來形容這種隊列,所以稱其爲用戶隊列,它是用函數dispatch_queue_create()創建的串行隊列。

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // main_queue 與主線程功能相同,串行  
  2. dispatch_queue_t mQueue =dispatch_get_main_queue();  
  3. // 全局隊列是併發隊列,並行  
  4. dispatch_queue_t gQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
  5. // 用戶隊列,串行  
  6. dispatch_queue_t oQueue = dispatch_queue_create("com.fcplay.MyQueue"NULL);  

    注意:dispatch_get_global_queue的第一個參數可爲DISPATCH_QUEUE_PRIORITY_LOW、DISPATCH_QUEUE_PRIORITY_DEFAULT和DISPATCH_QUEUE_PRIORITY_HIGH,分別代表高、中(默認)、低三種級別隊列,區別就是CPU時間片的不同,至於第二個參數,被保留了,所以一直是0就對了;dispatch_queue_create的第一個參數要保證其爲全局唯一,Apple官方就推薦使用反DNS的格式來命名,這純是爲了debug,這些名字會在崩潰日誌中被顯示出來,也可以被調試器調用,這在調試中會很有用,第二個參數目前還不支持,傳入NULL就行了。

    2、dispatch_async與dispatch_sync:這兩貨主要用於提交Job(作業,就是幹活),也就是說當我們想向一個隊列提交Job時,只要調用這個函數,傳入一個隊列和一個block就OK了。但他們也有很大區別,從名字上看就可以看出前者是異步的,後者是同步的,也就是說dispatch_async 函數在調用後會立即返回, block會在後臺異步執行;而dispatch_sync會等待block中的代碼執行完成才返回。

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  2.     [NSThread sleepForTimeInterval:2];  
  3.     NSLog(@"Call1");  
  4. });  
  5. NSLog(@"Call2");  

這肯定是先Call2再Call1;

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  2.     [NSThread sleepForTimeInterval:2];  
  3.     NSLog(@"Call1");  
  4. });  
  5. NSLog(@"Call2");  

這裏打印就是Call1再Call2。另外關於dispatch_sync這個方法,我們還需要知道一個原則,就是當你在方法中使用同步分配時,GCD就會把這個task放到你聲明的這個方法所屬的線程中去運行。例如上面dispatch_sync這段代碼,我們會發現這個任務會被放在主線程中運行。蘋果這這樣解釋的:As an optimization, this function invokes the block on the current thread when possible. 如果我們還是想讓它在一個併發隊列中執行,怎麼辦呢?藉助dispatch_async這貨吧,這裏卡你個例子:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_queue_t concurrentQueue =     
  2.         dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    
  3. dispatch_async(concurrentQueue,^{    
  4.     dispatch_sync(concurrentQueue, printFrom1To1000);     
  5.     dispatch_sync(concurrentQueue, printFrom1To1000);    
  6. });   

可以看到,先異步,然後裏面再嵌同步,這樣就可以保證你的同步分配的任務不會在主線程中運行了。

    3、dispatch group:可以用來將多個block組成一組以監測這些Block全部完成或者等待全部完成時發出的消息。它使用函數dispatch_group_create來創建,然後使用函數dispatch_group_async來將block提交至一個dispatch queue,同時將它們添加至一個組。例如很多情況下,我們可能需要在做完一系列工作後,最後來一個函數彙總,如下代碼:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. for(id obj in array)    
  2.     [self doSomethingIntensiveWith:obj];    
  3. [self doSomethingWith:array];   

這時候如果直接使用dispatch_async就悲劇了,如下面的寫法是達不到效果的:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    
  2. for(id obj in array)    
  3.     dispatch_async(queue, ^{    
  4.         [self doSomethingIntensiveWith:obj];    
  5.     });   
  6. [self doSomethingWith:array];  

被告訴我你直接用dispatch_sync,咱要異步啊。除了dispatch_sync我們還可以使用dispatch_group_t group配合dispatch_group_wait來實現:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    
  2. dispatch_group_t group = dispatch_group_create();    
  3. for(id obj in array)    
  4.     dispatch_group_async(group, queue, ^{    
  5.         [self doSomethingIntensiveWith:obj];    
  6.     });    
  7. dispatch_group_wait(group, DISPATCH_TIME_FOREVER);    
  8. dispatch_release(group);    
  9. [self doSomethingWith:array];   

這段代碼中,dispatch_group_wait用於阻塞等待對array中的obj都執行完doSomethingIntensiveWith後再往下執行,其實很多時候我們還可以用dispatch_group_notify實現更完美的方案:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    
  2. dispatch_group_t group = dispatch_group_create();    
  3. for(id obj in array)    
  4.     dispatch_group_async(group, queue, ^{    
  5.         [self doSomethingIntensiveWith:obj];    
  6.     });    
  7. dispatch_group_notify(group, queue, ^{    
  8.     [self doSomethingWith:array];    
  9. });    
  10. dispatch_release(group);  

    4、dispatch_apply:對於同步執行,GCD還有一個簡化方法叫做dispatch_apply,用於執行某個代碼片段N次,如:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_apply(5, globalQ, ^(size_t index) {  
  2. // 執行5次  
  3. });  

這裏還用上面例子可以這樣實現:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    
  2.     dispatch_apply([array count], queue, ^(size_t index){    
  3.         [self doSomethingIntensiveWith:[array objectAtIndex:index]];    
  4.     });    
  5.     [self doSomethingWith:array];   

異步怎麼辦?只要用dispatch_async函數將所有代碼推到後臺就行了,如下:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    
  2. dispatch_async(queue, ^{    
  3.     dispatch_apply([array count], queue, ^(size_t index){    
  4.         [self doSomethingIntensiveWith:[array objectAtIndex:index]];    
  5.     });    
  6.     [self doSomethingWith:array];    
  7. });   

    5、dispatch_barrier_async:是在前面的任務執行結束後它才執行,而且它後面的任務等它執行完成之後纔會執行。

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_queue_t queue = dispatch_queue_create("com.fcplay.MyQueue"NULL);   
  2. dispatch_async(queue, ^{   
  3.     [NSThread sleepForTimeInterval:2];   
  4.     NSLog(@"dispatch_async1");   
  5. });   
  6. dispatch_async(queue, ^{   
  7.     [NSThread sleepForTimeInterval:4];   
  8.     NSLog(@"dispatch_async2");   
  9. });   
  10. dispatch_barrier_async(queue, ^{   
  11.     NSLog(@"dispatch_barrier_async");   
  12.     [NSThread sleepForTimeInterval:4];   
  13.    
  14. });   
  15. dispatch_async(queue, ^{   
  16.     [NSThread sleepForTimeInterval:1];   
  17.     NSLog(@"dispatch_async3");   
  18. });  

運行結果如下,請注意執行的時間:

2014-02-14 14:20:33.967 gcdTest[45547:11203] dispatch_async1
2014-02-14 14:20:35.967 gcdTest[45547:11303] dispatch_async2
2014-02-14 14:20:35.967 gcdTest[45547:11303]dispatch_barrier_async
2014-02-14 14:20:40.970 gcdTest[45547:11303] dispatch_async3

    6、其他:還有dispatch_once_t、dispatch_time_t之類的看下面代碼

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // 一次性執行:  
  2. static dispatch_once_t onceToken;  
  3. dispatch_once(&onceToken, ^{  
  4.     // code to be executed once  
  5. });  
  6. // 延遲2秒執行:  
  7. double delayInSeconds = 2.0;  
  8. dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);  
  9. dispatch_after(popTime, dispatch_get_main_queue(), ^(void){  
  10.     // code to be executed on the main queue after delay  
  11. });  

三、使用例子

    1、簡單下載圖片並顯示

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{   
  2.     NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"];   
  3.     NSData * data = [[NSData alloc]initWithContentsOfURL:url];   
  4.     UIImage *image = [[UIImage alloc]initWithData:data];   
  5.     if (data != nil) {   
  6.         dispatch_async(dispatch_get_main_queue(), ^{   
  7.             self.imageView.image = image;   
  8.          });   
  9.     }   
  10. });   

當然你採用這樣的結構來寫:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. dispatch_queue_t concurrentQueue =    
  2.   dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    
  3. dispatch_async(concurrentQueue, ^{    
  4.   __block UIImage *image = nil;    
  5.   dispatch_sync(concurrentQueue, ^{    
  6.     /* Download the image here */    
  7.   });    
  8.   dispatch_sync(dispatch_get_main_queue(), ^{    
  9.     /* Show the image to the user here on the main queue*/    
  10.   });    
  11. });    

    2、帶提示下載圖片

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. //顯示loading  
  2. self.indicator.hidden = NO;  
  3. [self.indicator startAnimating];  
  4. //進入異步線程  
  5. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  6.     //異步下載圖片  
  7.     NSURL * url = [NSURL URLWithString:@"http://anImageUrl"];  
  8.     NSData * data = [NSData dataWithContentsOfURL:url];  
  9.     //網絡請求之後進入主線程  
  10.     dispatch_async(dispatch_get_main_queue(), ^{  
  11.         //關閉loading  
  12.         [self.indicator stopAnimating];  
  13.         self.indicator.hidden = YES;  
  14.         if (data) {//顯示圖片  
  15.             self.imageView.image = [UIImage imageWithData:data];  
  16.         }  
  17.     });  
  18. });  

    3、貼幾段斯坦福大學關於gcd的代碼,這段代碼逐步演示瞭如何修正錯誤,其中用到的既是串行隊列。這個是原始代碼:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. - (void)viewWillAppear:(BOOL)animated    
  2. {    
  3.     NSData *imageData = [FlickrFetcher imageDataForPhotoWithURLString:photo.URL];    
  4.     UIImage *image = [UIImage imageWithData:imageData];    
  5.     self.imageView.image = image;    
  6.     self.imageView.frame = CGRectMake(00, image.size.width, image.size.height);    
  7.     self.scrollView.contentSize = image.size;    
  8. }    

這個是採用gcdd的代碼,裏面有錯誤3處:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. - (void)viewWillAppear:(BOOL)animated    
  2. {    
  3.     dispatch_queue_t downloadQueue = dispatch_queue_create(“Flickr downloader”, NULL);    
  4.     dispatch_async(downloadQueue, ^{    
  5.          NSData *imageData = [FlickrFetcher imageDataForPhotoWithURLString:photo.URL];    
  6.          UIImage *image = [UIImage imageWithData:imageData];    
  7.          self.imageView.image = image;    
  8.          self.imageView.frame = CGRectMake(00, image.size.width, image.size.height);    
  9.          self.scrollView.contentSize = image.size;    
  10.     });    
  11. }    

第一個錯誤,UI更新只能在主線程中 Problem! UIKit calls can only happen in the main thread!
改正後如下:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. - (void)viewWillAppear:(BOOL)animated    
  2. {    
  3.     dispatch_queue_t downloadQueue = dispatch_queue_create(“Flickr downloader”, NULL);    
  4.     dispatch_async(downloadQueue, ^{    
  5.          NSData *imageData = [FlickrFetcher imageDataForPhotoWithURLString:photo.URL];    
  6.         dispatch_async(dispatch_get_main_queue(), ^{  
  7.              UIImage *image = [UIImage imageWithData:imageData];    
  8.              self.imageView.image = image;    
  9.              self.imageView.frame = CGRectMake(00, image.size.width, image.size.height);    
  10.              self.scrollView.contentSize = image.size;    
  11.          });    
  12. }); }   

第二個錯誤,NSManagedObjectContext並不是線程安全的,gcd中訪問成員變量有危險
Problem! NSManagedObjectContext is not thread safe,
so we can’t call photo.URL in downloadQueue’s t
改正後如下:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. - (void)viewWillAppear:(BOOL)animated    
  2. {    
  3.   NSString *url = photo.URL;   
  4.     dispatch_queue_t downloadQueue = dispatch_queue_create(“Flickr downloader”, NULL);    
  5.     dispatch_async(downloadQueue, ^{    
  6.         NSData *imageData = [FlickrFetcher imageDataForPhotoWithURLString:url];   
  7.         dispatch_async(dispatch_get_main_queue(), ^{    
  8.             UIImage *image = [UIImage imageWithData:imageData];    
  9.             self.imageView.image = image;    
  10.             self.imageView.frame = CGRectMake(00, image.size.width, image.size.height);    
  11.             self.scrollView.contentSize = image.size;    
  12. }); });    
  13. }    

第三個錯誤,隊列創建後沒有釋放,內存泄露,改正後:

[objc] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. - (void)viewWillAppear:(BOOL)animated    
  2. {    
  3.     NSString *url = photo.URL;    
  4.     dispatch_queue_t downloadQueue = dispatch_queue_create(“Flickr downloader”, NULL);    
  5.     dispatch_async(downloadQueue, ^{    
  6.         NSData *imageData = [FlickrFetcher imageDataForPhotoWithURLString:url];    
  7.         dispatch_async(dispatch_get_main_queue(), ^{    
  8.             UIImage *image = [UIImage imageWithData:imageData];    
  9.             self.imageView.image = image;    
  10.             self.imageView.frame = CGRectMake(00, image.size.width, image.size.height);    
  11.             self.scrollView.contentSize = image.size;    
  12. }); });    
  13.    dispatch_release(downloadQueue); //won’t actually go away until queue is empty }  

四、總結

    GCD極大地方便了iOS開發者使用多線程來完成數據與UI的交互,且充分利用了當今處理器的多核功能,既提高了效率又方便了使用。最後特別提醒NSManagedObjectContext並不是線程安全的,gcd中訪問成員變量有危險。另外dispatch_sync這貨有一個原則,就是當你在方法中使用同步分配時,GCD就會把這個task放到你聲明的這個方法所屬的線程中去運行。善用dispatch_syncdispatch_async能給予你很多意想不到的方便。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章