GCD 开发详情
目录
一、简介
二、dispatch Queue - 队列
三、dispatch Groups - 组
四、dispatch Semaphores - 信号量
五、dispatch Barriers - 障碍
六、dispatch sources - 系统源
七、dispatch I/O - I/O
八、总结
一、简介
GCD 的全称是 Grand Centre Dispatch 是一个强大的任务编程管理工具。通过GCD你可以同步或者异步地执行block、function。
二、dispatch Queue - 队列
queue是指先进先出的列表,是blocks的执行环境。这个环境可以是单线程的也可以使多线程这个取决于你创建的queue类型。如果是serial queue(串行队列)队列的话,queue内部是以单线程运行。被添加进去的blocks,按照它们被添加进去的顺序串行地执行。
如果是concurrent queue(并行队列),queue的内部线程个数由被添加的block执行的任务和当前系统有关。一般来说线程是会共用的,即是当一个block用完一个线程后,这个线程不会立马销毁会留着备用。这样就减少了创建线程的系统消耗。
我们下面看看怎么用queue相关的API
1. 创建queue
有两个创建queue的方法
一个是通过dispatch_queue_create手动创建queue,这个方法可以创建串行queue、并行queue
函数定义:
dispatch_queue_t dispatch_queue_create( const char *label dispatch_queue_attr_t attr);
参数 label 是指这个queue名字
参数 attr 可以指出需要创建的是什么类型的queue这里有两个值可选
DISPATCH_QUEUE_SERIAL ( NULL ) 串行queue
DISPATCH_QUEUE_CONCURRENT 并行queue
创建一个串行queue
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
另一个是通过 dispatch_get_global_queue 获取系统默认的queue,这个方法返回queue是并行的
dispatch_queue_t dispatch_get_global_queue( long identifier, unsigned long flags);
参数 identifier 用来指明queue的运行的优先级,可以取下值:
DISPATCH_QUEUE_PRIORITY_HIGH 高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级
DISPATCH_QUEUE_PRIORITY_LOW 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台
参数 flags 保留
另外还可以通过 dispatch_get_main_queue 来获取mian queue。mian queue 是执行在主线程上的一个queue,它与消息循环交错的运行。
通过调用 dispatch_get_current_queue 可以返回当前的queue。在一个被提交的block的内部调用 dispatch_get_current_queue 函数返回的是运行这个block的queue,在block之外调用返回的是main queue。
2. 把block添加到queue运行
GCD也提供也支持把一个function添加到queue 的api,这个api的特点是多了后缀_f,比如的dispatch_async/dispatch_async_f
一个block只有被提交到queue后才能被执行,一旦block被提交到queue就无法取消,而且我们是无法得知block执行的状态。所以在一些提交任务后需要取消的场景我们可以用NSOperation。
异步执行block
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL); dispatch_async(serial, ^{ NSLog(@"from queue"); });
dispatch_async 异步的把一个block提交到queue,立即返回
dispatch_sync 同步的把一个block提交到queue,阻塞当前线程直到block执行完成
void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
dispatch_after 在指定的时间到达时异步的提交block到queue执行。
when 参数是指延迟时间,是一个dispatch time 类型,可以通过dispatch_time、dispatch_walltime这两个函数生成
void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);
dispatch_once 在app的生命周期里只执行一次,常用于创建单例
3.void dispatch_set_target_queue( dispatch_object_t object, dispatch_queue_t queue);这个函数可以设置dispatch object的目标queue,dispatch object 可以使dispatch queue、dispatch source 、Dispatch I/O channels。目标queue就是dispatch object的finalizer(dispatch source)调用者,修改dispatch object的target queue会修改dispatch object如下的行为:
当被修改的dispatch object 为 Dispatch queue时,被修改的queue的优先级与target queue的优先级一致。如果修改一个serial queue 的 target queue为另一个serial queue 。为了下面方便描述,我们把被修改的queue叫做queueA,把target queue叫做queueB。那么在set target queue之后,先把一个blockA异步的提交到queueA,再把blockB异步的提交到queueB,这时候blockB会先于blockA执行。文字描述可能有点不清晰,我们直接来看代码
dispatch_queue_t serial1 = dispatch_queue_create("serial1", DISPATCH_QUEUE_SERIAL); dispatch_queue_t serial2 = dispatch_queue_create("serial2", DISPATCH_QUEUE_SERIAL); dispatch_async(serial1, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"blockA"); }); dispatch_async(serial2, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"blockA"); }); dispatch_set_target_queue(serial1, serial2); dispatch_async(serial1, ^{ NSLog(@"setted target : blockA "); }); dispatch_async(serial2, ^{ NSLog(@"setted target : blockB"); });
输出:
2017-01-06 21:02:32.268 GCDTest[7379:1744101] blockA
2017-01-06 21:02:32.268 GCDTest[7379:1744094] blockA
2017-01-06 21:02:32.269 GCDTest[7379:1744094] setted target : blockB
2017-01-06 21:02:32.269 GCDTest[7379:1744094] setted target : blockA
至于为什么是这个结果我现在还不清楚,我猜测应该是提交到queueA的block,等待它的target queue -> queueB的正在排队的办block都提交后,才把blockA提交到target block -> queueB。
当被修改的dispatch object 为 Dispatch source时,由新设置的target queue 来响应 Dispatch source的各个handle。
4.void dispatch_main( void) 这个函数的作用是暂停一下主线程,等待所有被提交到main queue的任务执行完成。一般的通过 UIApplicationMain (iOS), NSApplicationMain (OS X), or CFRunLoopRun 创建的程序是禁止调用这个函数的,所以这个函数我们了解一下即可。
看代码:
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"main queue 1"); }); dispatch_main(); // 如果不加这句代码的话,上面提交的block是不会执行的 return 0; }
5.上面提到dispatch_after函数,它的第一个参数需要一个dispatch_time_t类型,由dispatch_time、dispatch_walltime这两个函数生成(也可以取DISPATCH_TIME_NOW/DISPATCH_TIME_FOREVER这两个宏,第一个指现在马上,第二个指永远不)。现在我们来学习一下这两个函数
首先是dispatch_time_t dispatch_time( dispatch_time_t when, int64_t delta); dispatch_time函数需要输入两个参数
when 参数是指定一个开始值。一般取DISPATCH_TIME_NOW,意思是说从现在开始吧。
delta 参数的单位是纳秒,
dispatch_time 函数的作用就是返回一个相距 when 时间的 delta 个纳秒的时间间隔。比如一个延迟5秒的定时任务可以这样写:
// 从现在开始,5s后开始提交block到mian queue
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5); dispatch_after(when, dispatch_get_main_queue(), ^{ NSLog(@"do something."); });
dispatch_walltime函数同dispatch_time函数的功能一样的,只是两者的第一个参数类型不一样 ,前者参考的时间是 wall clock,后者参考时间四default clock。至于default clock 与 wall clock 的区别,可以自行了解。如果可以用一个不那么严谨的解释来描述的话,就如先描述:dispatch_time返回的是相距一个时间间隔(此时此刻)多少纳秒的时间间隔,dispatch_walltime返回的是一个相距特定日期时间(2017-01-08 22:30)多少纳秒的时间间隔。
下面给出使用dispatch_walltime的一个例子:
NSDate * date = [NSDate date]; NSLog(@"date:%@",date); // 2017-01-08 22:30:00 NSTimeInterval interval = date.timeIntervalSince1970; struct timespec time; time.tv_nsec = NSEC_PER_SEC * interval; time.tv_sec = 0; dispatch_time_t when = dispatch_walltime(&time,NSEC_PER_SEC*5);
// when 等于 从2017-01-8 22:30:00开始5s钟后,(2017-01-08 22:30:05 ) dispatch_after(when, dispatch_get_main_queue(), ^{ NSLog(@"do something."); });
三、dispatch Groups - 组
通过dispatch groups你可以同步一组blocks,你不需要管blocks执行顺序,在所有被提交到groups的blocks都执行完成后,你会得到通知。整个dispatch_groups 技术涉及3个概念,我们先理一下:
.block:执行任务的代码块,这个不用多说大家都知道
.queue:实际运行block代码的线程,上一节已经谈过了
.qroup:指把一些blocks,人工的组合在一起让它们成为一个组。
一句话总结dispatch groups技术:
创建一个group,并把一些blocks(需要指定执行queue)添加进来,等到所有的blocks都执行完后发通知到group。
我们直接来看代码:
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL); dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, serial, ^{ NSLog(@"serial block"); }); dispatch_group_async(group, concurrent, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"concurrent blockA"); }); dispatch_group_async(group, concurrent, ^{ NSLog(@"concurrent blockB"); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"group complete"); });
结果输出:
2017-01-09 09:58:56.137 GCDTest[9038:1830160] serial block 2017-01-09 09:58:56.137 GCDTest[9038:1830161] concurrent blockB 2017-01-09 09:58:57.142 GCDTest[9038:1830227] concurrent blockA 2017-01-09 09:58:57.142 GCDTest[9038:1830127] group complete
上面例子中有两个api
void dispatch_group_async( dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block); 异步的把一个block关联到一个group
void dispatch_group_notify( dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);把一个complete block提交给group,当group的所有关联blocks都执行完成后,会执行你提交的complete block。如果group中没有关联的block的,会立即执行complete block。
还有一个同步等待group执行完成通知的API : long dispatch_group_wait( dispatch_group_t group, dispatch_time_t timeout);
dispatch_group_wait会一直阻塞着,直到group所有关联的blocks都执行完成或者第二个参数提供的超时时间已经超时。
我们还可以通过这两个下面api来手动管理group的通知
dispatch_group_enter( dispatch_group_t group); // count 自增 1 (初始化为0)
dispatch_group_leave( dispatch_group_t group); // count 自减 1 (当count 为0时发complete 通知)
我们直接看代码
dispatch_queue_t concurrent1 = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t concurrent2 = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); dispatch_async(concurrent1, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"concurrent1 block "); dispatch_async(concurrent2, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"concurrent2 block "); dispatch_group_leave(group); }); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"group complete"); }); // 输出: // 2017-01-09 10:26:31.752 GCDTest[10009:1858381] concurrent1 block // 2017-01-09 10:26:32.756 GCDTest[10009:1858381] concurrent2 block // 2017-01-09 10:26:32.756 GCDTest[10009:1858042] group complete
四、dispatch Semaphores - 信号量
这个比较简单了,就是一个生产者消费者模式。
首先创建一个信号信号量并指定一个初始化值:
dispatch_semaphore_t dispatch_semaphore_create( long value);
当生产出一个产品时,我们增加一下信号量,下面这个函数返回值是当期有多少个可用的产品
long dispatch_semaphore_signal( dispatch_semaphore_t dsema);
当我们需要产品的时候,调用一下wait函数,如果返回0说明有产品可用,非0则相反
long dispatch_semaphore_wait( dispatch_semaphore_t dsema, dispatch_time_t timeout);
直接上代码:
// 生产者 dispatch_queue_t producer = dispatch_queue_create("producer", DISPATCH_QUEUE_CONCURRENT); // 消费者 dispatch_queue_t client = dispatch_queue_create("client", DISPATCH_QUEUE_CONCURRENT); // 信号量 dispatch_semaphore_t carFactory = dispatch_semaphore_create(0); dispatch_async(producer, ^{ for (int i=0; i < 5; i++) { [NSThread sleepForTimeInterval:i]; NSLog(@"[%d]output a car",i+1); dispatch_semaphore_signal(carFactory); } }); dispatch_async(client, ^{ while (1) { long ret = dispatch_semaphore_wait(carFactory, DISPATCH_TIME_NOW); if (ret == 0) { NSLog(@"get a car"); } } }); /* 输出 2017-01-09 11:30:54.754 GCDTest[17921:1948479] [1]output a car 2017-01-09 11:30:54.755 GCDTest[17921:1948486] get a car 2017-01-09 11:30:55.759 GCDTest[17921:1948479] [2]output a car 2017-01-09 11:30:55.760 GCDTest[17921:1948486] get a car 2017-01-09 11:30:57.764 GCDTest[17921:1948479] [3]output a car 2017-01-09 11:30:57.765 GCDTest[17921:1948486] get a car 2017-01-09 11:31:00.767 GCDTest[17921:1948479] [4]output a car 2017-01-09 11:31:00.767 GCDTest[17921:1948486] get a car 2017-01-09 11:31:04.770 GCDTest[17921:1948479] [5]output a car 2017-01-09 11:31:04.770 GCDTest[17921:1948486] get a car */
五、dispatch Barriers - 障碍
通过 dispatch_barrier 允许在并行queue中提交一个barrier block,假设我们把提交动作发生的这个时间点记做A点。完成提交barrier block动作后,会一直等待A点前提交的blocks都执行完毕才去执行barrier block,当barrier block被执行完毕后就开始按照concurrent queue既有的规制运行A点后提交的blocks。有两个提交barrier block的API,一个异步的,一个同步的。直接看代码
dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT); dispatch_async(concurrent, ^{ NSLog(@"block A"); }); dispatch_async(concurrent, ^{ [NSThread sleepForTimeInterval:2]; NSLog(@"block B"); }); // 异步提交 dispatch_barrier_async(concurrent, ^{ NSLog(@"barrier block "); }); // 同步提交 dispatch_barrier_sync(concurrent, ^{ NSLog(@"barrier block"); }); dispatch_async(concurrent, ^{ NSLog(@"block C"); }); // 结果 2017-01-09 13:03:17.203 GCDTest[19661:2010506] block A 2017-01-09 13:03:19.206 GCDTest[19661:2010513] block B 2017-01-09 13:03:19.207 GCDTest[19661:2010513] barrier block 2017-01-09 13:03:19.208 GCDTest[19661:2010513] block C
值得注意的是,只能把barrier block提交到自己通过dispatch_queue_crate创建的concurrent queue,如果提交到serial queue、global queue的话,barrier block不会起作用,与dispatch_sync、dispatch_async 无异
六、dispatch sources - 系统源
通过dispatch source 可以监控系统的一些事件,当这些事件发生时会把你的block提交到queue执行。包括下列事件
定时器事件
描述符(文件)事件
信号量事件
进程事件
在这里我只谈谈定时器怎么用的,其他事件在ios doc都有sample code;
dispatch source 有一套api可以用,我先来个概念快照
. dispatch_source_t dispatch_source_create( dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue) 创建一个dispatch source
. void dispatch_source_cancel( dispatch_source_t source); 取消一个dispatch source
. void dispatch_source_set_event_handler( dispatch_source_t source, dispatch_block_t handler); 设置event handle block
. void dispatch_source_set_cancel_handler( dispatch_source_t source, dispatch_block_t cancel_handler); 设置dispatch source 被取消的 handle block
. void dispatch_source_set_timer( dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway); // 给dispatch source设置一个定时器。
看过api快照后,你应该大概知道怎么用GCD创建一个定时器了 :先创建一个dispatch source ,然后给它设置 event、cancel handler 的block 和 设置timer属性,最后调用dispatch_resume执行dispatch source就行了。我们在代码里面详细说明
__block int count = 0; /* * dispatch_source_t dispatch_source_create( dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue) * 第一个参数指明这是一个timer,当然还可以指定其他类型 * 第二个参数是一个系统资源的句柄,比如文件句柄 * 第三个参数为flag * 第四个参数是handle block被提交到的queue */ dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); if (timer) { // 设置 timer dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1); // 设置 event handler dispatch_source_set_event_handler(timer, ^{ NSLog(@"timer fire"); if (count++ == 4) { // 取消dispatch source dispatch_source_cancel(timer); } }); // 设置 event handler dispatch_source_set_cancel_handler(timer, ^{ NSLog(@"time cancel."); }); // 启动 dispatch source // 因为create后还需要配置一些行为,所以需要手动resume dispatch source dispatch_resume(timer); // 注意: // 这里需要保存一下timer到类变量,不然timer是局部变量运行到函数尾部是,这个定时器也就没了 self.timer = timer; }
七、dispatch I/O - I/O
八、总结
最后再来看看dispatch_object的管理:
//dispatch_object 对象的内存管理,用ARC技术的话,这两个函数就没有用了 void dispatch_release( dispatch_object_t object); void dispatch_retain( dispatch_object_t object);
// 注意 void dispatch_resume( dispatch_object_t object); // counting 减1 等于0就回复 void dispatch_suspend( dispatch_object_t object); // counting 加1 大于0就挂起 // 可以给dispatch_object设置一个context用于在各个任务之间共享 void * dispatch_get_context( dispatch_object_t object); // 获取 context void dispatch_set_context( dispatch_object_t object, void *context); // 设置 context // 可以给dispatch_object设置一个执行完成的回调函数 void dispatch_set_finalizer_f( dispatch_object_t object, dispatch_function_t finalizer);