关于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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. for(id obj in array)    
  2.     [self doSomethingIntensiveWith:obj];    
  3. [self doSomethingWith:array];   

这时候如果直接使用dispatch_async就悲剧了,如下面的写法是达不到效果的:

[objc]  view plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. dispatch_apply(5, globalQ, ^(size_t index) {  
  2. // 执行5次  
  3. });  

这里还用上面例子可以这样实现:

[objc]  view plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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 plain copy 在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能给予你很多意想不到的方便。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值