GCD全称Grand Central Dispatch,是一套低层级API。它允许程序将任务切分成多个单一任务,然后提交至工作队列并发或串行的执行。它不是Cocoa框架的一部分。GCD提供了高度集成的事件控制系统:可以设置句柄来响应描述文件、mach ports、进程、计时器、信号、用户生成事件。这些句柄通过并发实现。GCD紧密结合block实现,简单易用,功能强大。
相比于传统多线程的优势:
1. 易用:传统的thread是基于运算实现的,而GCD基于unit of work,可以控制线程的等待任务结束、监控文件描述符、周期性执行代码和工作挂起等任务。同时,基于block的接口使用更方便。
2. 效率:GCD的实现很简单,在一些很好资源的线程中更实用和快速。这也导致GCD易用的原因,不必担心太多的效率问题。
3. 性能:GCD根据系统负载来自动增减线程数量,减少了上下文切换以及增加了计算效率。
Dispatch Objects
GCD是由纯C代码实现,但它被组建成面向对象的风格。GCD对象被称为dispatch object。dispatch object像Cocoa对象一样计数,使用dispatch_release和dispatch_retain函数来操作计数引用进行性内存管理。但与Cocoa对象不同,dispatch object不参与垃圾回收系统,必须手动释放内存。
dispatch queues和dispatch sources可以被挂起和恢复,可以有一个相关的上下文指针,可以有一个任务完成触发函数。
Dispatch Queues
GCD的基础概念就是dispatch queue。dispatch queue 是一个对象,它可以接受任务,以先到先执行的顺序执行。可以是并发或串行的,并发基于系统负载合理的执行,串行队列同一时间只执行单一任务。GCD有三种队列类型:
1. Main:即主线程队列,可以通过调用dispatch_get_main_queue()来获得,这是一个串行队列。
2. Global:全局队列是并发队列,并由整个进程共享。全局队列有高、中(默认)、低三个优先级,可以通过dispatch_get_global_queue函数传入优先级来获取队列。
3. 自定义队列:队列可以通过函数dispatch_queue_create创建,函数第一个参数是一个标签,苹果建议使用倒置域名命名,这些标签会在崩溃日子中显示,也可以被调试器调用,在调试中很有用。这些队列是串行的。
Dispatch_async和Dispatch_sync
dispatch_async()函数会立即返回,block会根据相应的queue异步执行。block中一般都是处理耗时比较长的代码,当然,很有可能还需要更新界面,需要回到主线程中:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self loadImages];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUI];
});
});
dispatch_sync()函数会等待block在相应的queue中执行完后返回,比如在一个线程中需要获取界面的某些信息,需要回到主线程中:
__block NSString *mailString;
dispatch_sync(dispatch_get_main_queue(), ^{
mailString = mailTextField.text;
});
但是,如果获取界面信息的耗时比较长,容易阻塞当前线程。我们可以中止后台线程,异步取出主线程信息,然后再将信息交给后台现场处理:
dispatch_queue_t currentQueue = ...
dispatch_async(dispatch_get_main_queue(), ^{
NSString *mailString = mailTextField.text;
dispatch_async(currentQueue, ^{
//deal with mailString
});
});
GCD锁
在传统代码中,锁的实现有NSLock和@synchronized(self)等。在GCD中,使用dispatch_queue_t代替,声明的对象必须由dispatch_queue_create创建,可以通过dispatch_async或dispatch_sync将数据访问和更新的代码封装起来。同时,dispatch queue是非常轻量级的,可以放心大量使用:
dispatch_queue_t queue = dispatch_queue_create("com.dds.nameQueue", DISPATCH_QUEUE_SERIAL);
- (void)setName:(NSString *)name
{
dispatch_async(queue, ^{
_name = [name copy];
[self updateSomething];
});
}
- (NSString *)name
{
__block NSString *tempName;
dispatch_sync(queue, ^{
tempName = _name;
});
return tempName;
}
使用dispatch_queue_t作为锁的优点:
1. 平行计算。比如setName函数中,如果udpateSomething耗时太长,使用dispatch_async异步操作,主线程不会卡顿,大量工作放置在后台处理。
2. 安全。使用lock,需要解锁,如果代码中有直接返回等情况,解锁就不会被调用。使用GCD,队列通常会持续进行,你必须交回控制权。
3. 控制。使用GCD我们可以挂起和恢复dispatch queue,而这是基于锁的方法所不能实现的。我们还可以将一个用户队列指向另一个dspatch queue,使得这个用户队列继承那个dispatch queue的属性。使用这种方法,队列的优先级可以被调整——通过将该队列指向一个不同的全局队列,若有必要的话,这个队列甚至可以被用来在主线程上执行代码。
4. 集成。GCD的事件系统与dispatch queue相集成。对象需要使用的任何事件或者计时器都可以从该对象的队列中指向,使得这些句柄可以自动在该队列上执行,从而使得句柄可以与对象自动同步。