在2011的WWDC上,苹果推出了GCD,从此多线程增加了一种新的方法。GCD要求运行在iOS4.0版本以上或者OS X10.6版本以上。GCD是Grand Central Dispatch的缩写,是一组用于实现并发编程的C接口。GCD是基于Objective-C的Block的特性开发的,基本的业务逻辑和NSOperation很像。都是添加一个任务到一个队列,由系统来负责线程的生成和调度。因为直接使用Block,所以使用起来很是方便,降低了多线程开发的门槛。
还是先看一下代码,和多线程系列(1)里面同一个例子,用GCD实现如下:
- (void)viewDidLoad { [super viewDidLoad]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self downloadImage:IMAGE_URL]; }); }
GCD的调用接口非常简单,就是将任务提交到Queue里面。
dispatch_async函数是异步非阻塞的,调用后会立刻返回,工作由系统在线程池中分配线程去执行。有异步的当然也有同步的,dispatch_sync就是同步的阻塞的API,会一直到添加的任务完成才会返回。
GCD实现多线程确实很简单,不需要了解多线程中的很多细节,而且效率也高。不过disaptch_queue有一些特殊的地方,实际使用中需要了解的多一些。dispatch_queue有串行运行和并行运行两种,顾名思义,串行运行就是任务顺序执行,完成一个然后执行下一个,每次只有一个任务在运行;并行运行就是各个任务可以同时运行,同时有多少任务可以并行是根据系统当时的负载决定的,这个开发者不用关心。
系统提供了3中类型的dispatch queue:
1. main queue
这实际上就是主线程的队列,所以很明显,这是一个串行的queue,所有加入main queue的任务都会发动主线程运行,所以加入任务时需要注意不要加入长时间运行的任务。
2. Global queue
我们实际开发中最常用的队列,是并发队列。并且有high、default、low三个优先级(每个优先级都对应一个独立的queue)。通过dispatch_get_global_queue这个API可以获得queue。
3. 自定义queue
dispatch queue是可以自己创建的,通过dispatch_queue_create这个API来创建,dispatch_queue_create(const char *label, dispatch_queue_attr attr)这个API的第一个参数是queue的名字,要求不能重复,所以很多时候和java一样,推荐用倒写的域名,第二个参数是建立的queue的类型。这里要指出,在iOS4.3之前,只能建立串行的queue,参数就是传递DISPATCH_QUEUE_SERIAL,iOS4.3之后可以建立并行的queue了,参数是DISPATCH_QUEUE_CONCURRENT。
看到create就会牵涉到内存的管理问题,GCD的内存管理同样是用引用计数的方式,不过并不纳入iOS的内存管理,所以是需要开发者手动管理的(无论是不是ARC)。
由于有着不同类型的队列,dispatch_async也可以嵌套使用,还是以同样的例子,我们也可以这样写:
- (void)viewDidLoad { [super viewDidLoad]; __block UIImage *_image; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:IMAGE_URL]]; _image = [[UIImage alloc] initWithData:data]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = _image; }); }); }
这样就在一段代码里面实现了所有的功能,包括后台下载,下载之后刷新UI,而且简单清晰。
还有一些常用的API介绍如下:
dispatch_get_current_queue()获取当前队列
dispatch_queue_get_label()获取队列的名字,如果队列没有名字,返回NULL
dispatch_set_target_queue()设定给定对象的目标队列
dispatch_main()会阻塞主线程等待主队列main queue中的Block执行结束。
有时我们会遇到运行一系列的任务,当任务全部结束后运行另一个特殊的任务这种场景。如果我们用dispatch_sync方法来串行运行所有的任务可以确定运行的先后顺序,但效率就会大大降低;但dispatch_async是异步非阻塞的,所以代码如下写是没用的,不能保证结束所有任务后那个特殊任务的运行时间点。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for(id obj in array) dispatch_async(queue, ^{ [self doWork:obj]; }); [self doneWork];
针对这种情况,GCD提供了dispatch group,可以将一组任务集合在一起,等待这组任务完成后再继续,上面的场景,代码应该写成下面的样子:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); for(id obj in array) dispatch_group_async(group, queue, ^{ [self doWork:obj]; }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_release(group); [self doneWork];
方法很简单,就是将并发的任务用dispatch_group_async异步添加到一个Group和全局队列中,dispatch_group_wait会等待这些工作完成后在返回。这样就实现了任务的顺序运行,不过dispatch_group_wait是会阻塞线程的,所以如果是主线程,这个API是不能调用的,那么我们该怎么办呢?
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); for(id obj in array) dispatch_group_async(group, queue, ^{ [self doWork:obj]; }); dispatch_group_notify(group, queue, ^{ [self doneWork]; }); dispatch_release(group);
答案还是很简单,换一个API,使用dispatch_group_notify这个方法即可。
有的时候我们要同步执行对数组元素的逐个操作,GCD提供了一个简单的dispatch_apply方法:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply([array count], queue, ^(size_t index){ [self doWork:obj:[array objectAtIndex:index]]; }); [self doneWork];
在使用dispatch_async方法提交并行的任务时,是无法确定任务的执行顺序的,但有时我们确实需要某些工作在某个工作完成之后执行,那么可以使用Dispatch Barrier接口来实现。
dispatch_async(queue, block1); dispatch_async(queue, block2); dispatch_barrier_async(queue, block3); dispatch_async(queue, block4); dispatch_async(queue, block5);
dispatch_barrier_async是异步的,调用后立刻返回。这样的写法会保证block1和block2并行执行完成后才会执行block3,完成后再会并行运行block4和block5。
请注意,这里的queue是一个并行队列,而且是自定义的那种。
作为苹果推出的多线程的神器,GCD的内容当然远远不止这些。不过通过介绍的最最常用的这些,我们已经可以管中窥豹了。GCD针对各种不同的需求考虑的很全面,并给出了相关的解决方案。开发者使用GCD应该说是很容易的,所以真正需要关心的就变成了任务怎么划分,怎么运行,是串行还是并行等等。
附上苹果的Grand Central Dispatch(GCD)Reference文档,需要深入了解的请参考。