Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。
GDC是苹果为多核心处理器开发的一套异步调度机制。苹果官方文档中是这样介绍它的:
Grand Central Dispatch (GCD) comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in iOS and OS X.
QueueGCD 的异步核心是队列,系统会有自己的主队列和全局队列,任何在主队列进行的任务都会阻塞 UI 事件。
获取和创建队列的函数:
dispatch_get_main_queue:
获取主队列。dispatch_get_global_queue:
获取全局队列
dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);
其中第一个参数为优先级,可选的优先级有以下几个:
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
从上到下优先级依次降低,该函数的第二个参数为系统保留,每次都传入 0 即可。通常我们第一个参数也传入 0 来获取默认的全局队列。
dispatch_queue_create:
创建一个队列
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr);
第一个参数为标识符,通常用倒过来写的域名 (eg. com.apple.myqueue
) ,第二个参数为队列类型,有下列类型可选:
DISPATCH_QUEUE_SERIAL
DISPATCH_QUEUE_SERIAL_INACTIVE
DISPATCH_QUEUE_CONCURRENT
DISPATCH_QUEUE_CONCURRENT_INACTIVE
顾名思义,前两个是串行的,后两个是并行的。如果传入 NULL等同于DISPATCH_QUEUE_SERIAL,
表示串行队列。
1)并行队列global dispatch queue,通过dispatch_get_global_queue获取,由系统创建三个不同优先级的dispatch queue。并行队列的执行顺序与其加入队列的顺序相同。
2)串行队列serial queues一般用于按顺序同步访问,可创建任意数量的串行队列,各个串行队列之间是并发的。
当想要任务按照某一个特定的顺序执行时,串行队列是很有用的。串行队列在同一个时间只执行一个任务。我们可以使用串行队列代替锁去保护共享的数据。和锁不同,一个串行队列可以保证任务在一个可预知的顺序下执行。
串行队列通过dispatch_queue_create创建,可以使用函数dispatch_retain和dispatch_release去增加或者减少引用计数。
有了队列,我们就可以向其中分发任务了,下面是分发任务时需要的函数:
dispatch_async:
将执行体放入队列然后立即返回,如果队列是主队列,那么就相当于延后执行代码。dispatch_sync:
将执行体放入队列并执行,等待执行完毕再返回。在主线程中,队列坚决不能是主队列,不然主线程被挂起来等待任务执行结束,然而这个任务就是要在主线程执行,那么这个任务永远不会执行,主线程也永远不会被唤醒。同理队列也不能是调用dispatch_sync
的当前队列,同样会产生死锁。dispatch_apply:
迭代执行,第一个参数是迭代次数,第二个参数是目标队列,第三个参数是欲执行的 block,block 的参数就是当前迭代的下标。注意,这个函数将会等待所有执行体完毕后才返回。适当情况下可以用这个函数来代替 for-loop 以提高性能。
通常,我们在 Global Queue 中执行一些耗时操作,然后在 Main Queue 中将结果更新到 UI。
// 后台执行:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// something
});
// 主线程执行:
dispatch_async(dispatch_get_main_queue(), ^{
// something
});
// 自定义dispatch_queue_t
dispatch_queue_t urls_queue = dispatch_queue_create("com.aaa.bbb", NULL);
dispatch_async(urls_queue, ^{
// your code
});
dispatch_release(urls_queue);
Time
在 GCD 有几个比较重要的函数与时间相关:
dispatch_time
: 创建一个时间对象
第一个参数是参考时间,第二个参数是时间增量,那么最终返回的时间就是 (参考时间 + 增量)
,通常使用方法有下面几种:
//当前时间后 100 毫秒
dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_MSEC * 100));
//当前时间后 3 秒
dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC * 3)):
其中 NSEC_PER_MSEC 和 NSEC_PER_SEC 是时间计量单位,分别表示毫秒和秒。
dispatch_after
: 在指定时间后执行
这个函数将立即返回,等到指定时间后才会在指定的列队中执行指定任务。
// 延迟2秒执行:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// code to be executed on the main queue after delay
});
Group
如果需要批量执行一批任务,并且想在所有这批任务结束后得到通知,这时就需要用到 Group 了。
dispatch_group_create
:创建一个组,无任何参数。dispatch_group_async
:同dispatch_async
,只不过第一个参数前多了一个 group ,即将任务归为这一组。dispatch_group_enter
:增加组的计数,此时你不需要提交执行体,等待dispatch_group_leave
被调用后则表示一个任务完成,可嵌套,总之保证两者调用次数平衡即可。dispatch_group_notify
:在该组任务都完成后在指定列队中调用一个回调函数。dispatch_group_wait
:阻塞线程直到该组任务都完成,或指定时间超时后返回。
// 合并汇总结果
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// 并行执行的线程一
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// 并行执行的线程二
});
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// 汇总结果
});
Once
- dispatch_once:执行某个事件一次且仅只有一次。
dispatch_once(dispatch_once_t *predicate,
DISPATCH_NOESCAPE dispatch_block_t block);
第一个参数是once
,第二个是首次执行的block块,调用起来是这样的
// 一次性执行:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
dispatch_once
是线程安全的,它可以保证在多个线程同时调用的时候,dispatch_once
只会执行一次,并且所有的线程在dispatch_once
返回之前都会进入等待状态。
使用dispatch_once
实现单例:
+ (AccountManager *)sharedManager {
static AccountManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}