iOS 之GCD

NSThread和NSOperation就不多说,NSOperation就是将GCD封装成对象来便于开发者调用。苹果官方对GCD是这样说明的:开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。Dispatch Queue是执行处理的等待队列,我们可以通过dispatch_async等API,在block语法中记述想要执行的处理并将其追加到Dispatch Queue中,Dispatch Queue是按照追加的顺序 进行处理,先进先出FIFO。


图1 通过dispatch queue执行处理

用代码表示就是:

dispatch_async(queue,^{

//想执行的任务,这样执行就是在另一个新开辟的线程中

});

在执行处理时候是有两种Dispatch Queue,一种是Serial Dispatch Queue串行调度队列,这个是等待现在执行中的事件处理结束,另一种是Concurrent Dispatch Queue并发调度队列,这个是不等待现在执行中的事件处理结束。


图2 Serial Dispatch Queue

那我们来举个???? 来区分一下两种调度队列的区别:

有ABC三个任务等待执行,

dispatch_async(queue,A)

dispatch_async(queue,B)

dispatch_async(queue,C)

如果queue使用Serial Dispatch Queue,则同时执行的处理数只有一个,A执行结束才能执行B,B执行结束才能执行C,A->B->C顺序执行。

如果queue使用Concurrent Dispatch Queue,这样不用等待现在执行中的处理结束,可以并行执行多个处理,但并行执行的处理数取决于iOS和OS X的CPU核数以及CPU负荷等当前系统状态。所谓“并发执行”,就是使用多个线程同时执行多个处理。


图3 Serial开辟线程数量


图4 Concurrent开辟线程数量

现在各位看官应该可以明白Serial Dispatch Queue和Concurrent Dispatch Queue的大致区别了,那么我们怎样才能得到这些Dispatch Queue呢?一共分为两种。

第一种:通过GCD的API的dispatch_queue_create函数生成Dispatch Queue

dispatch_queue_create

生成Serial Dispatch Queue代码:

dispatch_queue_t  exampleSerialDispatchQueue = dispatch_queue_create("exampleSerialDispatchQueue.gcd.example.com",NULL)

我们先讲一下Serial Dispatch Queue生成个数的问题,上面也讲到了Serial Dispatch Queue只会生成一个线程,同一时间执行一个处理,如果想要实现Concurrent Dispatch Queue并发执行的效果,我们可以将多个任务放到多个Serial Dispatch Queue。


图5 多个Serial Dispatch Queue

但是如果有N个的任务,就需要创建N个线程,就会消耗大量内存,降低系统的响应性能。所以当需要遇到这种需求还是首先使用Concurrent Dispatch Queue。

接下来要讲解一下这个函数的组成,dispatch_queue_create的第一个参数指定了Dispatch Queue的名称,推荐使用name,虽然使用NULL也可以,但是为了后期调试维护还是给定一个name。第二个参数为NULL,则生成Serial Dispatch Queue。如果要生成Concurrent Dispatch Queue时,第二个参数要使用DISPATCH_QUEUE_CONCURRENT。

dispatch_queue_t  exampleConcurrentDispatchQueue = dispatch_queue_create("exampleSerialDispatchQueue.gcd.example.com",DISPATCH_QUEUE_CONCURRENT),

另外需要注意的点是:虽然有ARC编译器自动管理内存这一优秀技术,但生成的Dispatch Queue必须由程序员主动释放。(iOS6.0 以后ARC支持释放)

dispatch_release(exampleSerialDispatchQueue) 释放

dispatch_retain(exampleSerialDispatchQueue) 持有

第二种:直接使用系统提供的标准Dispatch Queue

Main Dispatch Queue和Global Dispatch Queue

Main Dispatch Queue是在主线程中执行的Dispatch Queue,也就是Serial Dispatch Queue。


图6 Main Dispatch Queue

Global Dispatch Queue不需要通过dispatch_queue_create函数来逐个生成Concurrent Dispatch Queue,只要获取Global Dispatch Queue就行了。Global Dispatch Queue的执行优先级有四种:

Global Dispatch Queue(High Priority) Global Dispatch Queue  优先级高

Global Dispatch Queue(Default Priority) Global Dispatch Queue  优先级默认

Global Dispatch Queue(Low Priority) Global Dispatch Queue  优先级低

Global Dispatch Queue(Backgroud Priority) Global Dispatch Queue  优先级后台

附上:Main Dispatch Queue的说明

Main Dispatch Queue  Serial Dispatch Queue  主线程执行

Main Dispatch Queue的获取方法:

dispatch_queue_t  mainDispatchQueue = dispath_get_main_queue();

Global Dispatch Queue的获取方法:

dispatch_queue_t globalDispatchQueueHigh = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0)  //高优先级

dispatch_queue_t globalDispatchQueueDefault = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)  //默认优先级

dispatch_queue_t globalDispatchQueueLow = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0)  //低优先级

dispatch_queue_t globalDispatchQueueBackgroud = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_GACKGROUND,0)  //高优先级

下面举出一个简单的示例代码:

//在默认优先级的Global Dispatch Queue中执行block

dispatch_async(dispatch_get_global_queue,0),^{

//并行想要执行的代码

//在Main Dispatch Queue中执行block

dispatch_async(dispatch_get_main_queue(),^{

//想要在主线程中执行的代码 如刷新UI

});

});

dispatch_after

此函数是可以实现一些延迟处理的功能,比如产品汪想要点击一个按钮,延迟两秒钟再执行操作这个场景。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_ROW, 3ull * NSEC_PER_SEC);

dispatch_after (time ,dispatch_get_main_queue(),^{

//等待三秒之后要执行的操作

});

"ull"是C语言的数值字面量,是显示表明类型时使用的字符串(表示“unsigned long long”),如果NSEC_PER_SEC替换为NSEC_PER_MSEC则是以毫秒为单位进行计算。

DISPATCH_TIME_NOW表示现在的时间,到这里,需要注意一下,dispatch_after函数并不是在指定时间后执行处理,而是在指定时间追加处理到Dispatch Queue。

dispatch group

有时候我们会有这种需求,在刚进去一个页面需要发送两个请求,并且某种特定操作必须在两个请求都结束(成功或失败)的时候才会执行,最low的办法第二个请求嵌套在第一个请求结果后在发送,在第二个请求结束后再执行操作。还有就是只使用一个Serial Dispatch Queue,把想要执行的操作全部追加到这个Serial Dispatch Queue中并在最后追加某种特定操作,颇为复杂操作。但是呢,我们这里介绍更高级的办法使用dispatch group。

我们将ABC三个任务block追加到Global Dispatch Queue,ABC全部执行完,会执行dispatch_group_notify中的block。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_group_t  group = dispatch_group_creat();

dispatch_group_async(group,queue,^{执行任务A});

dispatch_group_async(group,queue,^{执行任务B});

dispatch_group_async(group,queue,^{执行任务C});

dispatch_group_notify(group,dispatch_get_main_queue(),^{执行最终的特定操作});

ABC的执行顺序不固定,因为是Global Dispatch Queue即Concurrent Dispatch Queue多个现场并行执行。

上面的dispatch_group_notify(group,dispatch_get_main_queue(),^{执行最终的特定操作});操作还可以更改为

dispatch_group_wait(group,DISPATCH_TIME_FOREVER);

dispatch_group_wait第二个参数指定为等待的时间(超时),属于dispatch_time_t类型,在这里使用DISPATCH_TIME_FOREVER,意味着永久等待。如果dispatch group的处理尚未结束,就会一直等待。

如果指定等待时间为1秒如下:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NESC_PER_SEC);

long result = dispatch_group_wait(group,time);

if(result == 0) { dispatch group的全部处理执行结束}

else { dispatch groupe的某一处理还在执行中};

但是呢上面这种dispatch_group的排列执行方式,是不会考虑block块内部的异步请求情况的,它只能保证把block内的非异步直观代码执行完,所以如果ABC三个任务中如果有执行异步的请求,那么在dispatch_group_notify最终任务执行中,那个异步请求不一定毁掉结束。

在这里给大家介绍针对这种问题另一个API。

dispatch_group_enter/dispatch_group_leave

dispatch_group_t group = disoatch_group_creat();

dispatch_group_enter(group);

dispatch_async(dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{

//在这里执行异步请求A

并且在执行结束代码(成功或失败)中写上dispatch_group_leave(group);

});

dispatch_group_enter(group);

dispatch_async(dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{

//在这里执行异步请求B

并且在执行结束代码(成功或失败)中写上dispatch_group_leave(group);

});

dispatch_group_notify(group,dispatch_get_main_queue(),^{执行最终的特定操作});

上面这种做法当执行到dispatch_group_notify,一定是AB两个异步请求都加在结束了。dispatch_group_enter(group)和dispatch_group_leave(group);必须成对出现,编译器会强制识别当出现dispatch_group_leave全部结束才执行dispatch_group_notify,即使这种,档异步执行了AB两个请求,也不能保证请求执行结束的先后顺序,如果任务B的请求参数包含请求A的返回参数,那么只能用最Low的办法将请求B嵌套在A中执行,当然如果你有更好的办法欢迎提出来哈。

dispatch_apply

这个函数可以给定指定的次数将block追加到指定的Dispatch Queue中,并且等待全部结束处理执行。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_apply(10,queue,^(size_t index){

NSLog(@“%zu”,idnex);

});

NSLog(@“done”);

执行结果:4,1,0,3,5,2,6,8,9,7,done

第一个参数是重复次数,第二个参数是追加对象的dispatch queue,第三个参数是追加的处理。dispatch_apply可以做遍历数组的操作,不必一个一个写for循环。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_apply([array count],queue,^(size_t index)){

NSLog(@"%zu:%@",index,[array objectAtIndex:index]);

}

dispatch_suspend/dispatch_resume

如果有时候你希望不执行已经追加到dispatch queue中的一些处理,比如这些处理会对验算结果造成影响。在这种情况,只需要挂起dispatch_queue即可,需要时再恢复。

dispatch_suspend函数挂起指定的dispatch_queue:

dispatch_suspend(queue);

dispatch_suspend函数恢复指定的dispatch_queue:

dispatch_resume(queue);

函数对已经执行的处理没有影响,挂起后,追加到dispatch queue中但尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。

dispatch_once

这个函数保证在应用程序执行中只执行一次指定处理的API。下面先举一个不用dispatch_once的常见的写法:一个对象A

if(!对象A){

对象A =[ [对象A  alloc] init];

}

使用dispatch_once函数为:

static dispatch_once_  onceToken;

dispatch_once( &onceToken,^{

对象A =[ [对象A  alloc] init];

});

通过dispatch_once创建的即使在多线程环境下执行也百分百安全。

另外关于GCD的API还有诸如:dispatch_set_target_queue,dispatch_barrier_async, disapatch_sync,dispatch Semaphore等 在这里不多做说明,不常用到用到的话可以查官方API。

再次翻阅了一下GCD发现这篇写的不错,稍微修改了一下拿来分享。

推荐阅读更多精彩内容