本文假设你有一定的 GCD 和 Swift 基础。
iOS 下的多线程编程技术从底层往上分别是 NSThread、NSOperation、Grand Central Dispatch(GCD),这三个技术越往后抽象度越高,使用也越简单。GCD 无论在 Objective-C 时代,还是后 OC 的 Swift 时代,都是 iOS 开发者使用最多的多线程编程技术。
GCD 是使用 C 语言编写的。多年以来,一直使用 Objective-C 的开发者们很习惯 GCD 的底层 C 语言式的代码。这种风格一直保留到 Swift 推出两年后,到了 Swift 2 。无论 Swift 迭代了多少回,GCD 依然保留其原始的 C-Style。终于,到了 2016 年,全力研发 Swift 的苹果终于想起了 GCD 这么个东西,在 WWDC 2016 推出的 Swift 3 中完全更改了 GCD 的代码风格。
回顾
在 Swift < 3 中,我们写一个 GCD 代码可能是这样的:
let queue = dispatch_queue_create("Kenneth", nil)
dispatch_async(queue) {
print("Hello World")
}
我们创建一个串行队列,然后指派一个输出 Hello World 的异步任务给这个队列,一切看上去都很自然。然而唯一的缺点就是,这太不 Swift 了。
Swift < 3 中,Swift 标准库里面的 libdispach 是一从 C 导入的函数的集合。这也导致了在 Swift < 3 中使用 GCD 非常不符合 Swift 的语法,而且这些函数名在 Swift 中看上去也很怪异。
What's new?
我们再看看 Swift 3 中,上面的代码是如何写的:
let queue = DispatchQueue(label: "Kenneth")
queue.async {
print("Hello World")
}
很好,这很 Swift,或者说这很 OOP。
我们先简单看一看和之前到底有何不同。
- 在之前的 GCD 中,我们写函数的顺序是,先确定异步还是同步操作,然后把队列作为参数传入函数,然后指派一个任务闭包。在 Swift 3 中,这个顺序反了过来,我们先指定一个队列,然后再选是异步还是同步操作,这更符合面向对象的语法特点。
- 变量名更符合 Swift API 的设计规范
仔细看看
命名方式
从上述的引言中,我们可以看到在 Swift 3 中,我们熟悉的 GCD 类型名已经发生了比较大的改变,下划线被去掉,使用了大驼峰式命名法,不过好在他们还没有脱离原来的含义,下面是一张对照表:
C | Swift |
---|---|
dispatch_object_t | DispatchObject |
dispatch_queue_t | DispatchQueue |
dispatch_group_t | DispatchGroup |
dispatch_data_t | DispatchData |
dispatch_io_t | DispatchIO |
dispatch_semaphore_t | DispatchSemaphore |
dispatch_source_t | DispatchSource |
dispatch_time_t | DispatchTime, DispatchWalltime |
C | Swift |
---|---|
dispatch_fd_t | Int32 |
dispatch_block_t | () -> () |
dispatch_queue_attr_t | DispatchQueueAttributes |
队列
主队列
主队列通常被用来做我们 App 的 UI 更新操作,在之前的版本,我们通过这个函数获取主队列:
dispatch_get_main_queue()
现在我们用这个属性:
DispatchQueue.main
全局队列
在 iOS < 8 之前,我们有四种优先级的全局队列,他们分别是:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
在 iOS >= 8 之后,优先级的概念被苹果使用 QoS 替代了,Swift 3 中也一样。我们不再使用优先级,而是使用 QoS 来描述全局队列。简单地说,这两者之间的对应关系是这样的:
Priority | DispatchQoS |
---|---|
DISPATCH_QUEUE_PRIORITY_HIGH | .userInitiated |
DISPATCH_QUEUE_PRIORITY_DEFAULT | .default |
DISPATCH_QUEUE_PRIORITY_LOW | .utility |
DISPATCH_QUEUE_PRIORITY_BACKGROUND | .background |
在 Swift 3 中,获取全局队列需要使用这个方法:
DispatchQueue.global(qos: DispatchQoS.*)
我们将 QoS 传入 global() 方法,实际上就像指定它的优先级。当然你也可以不指定,默认就是 default。
DispatchQueue.global() // 等于 DispatchQueue.global(qos: .default)
创建队列
就像在前言中所举的例子,有时候我们想创建一个队列,之前我们是这么写的:
let queue = dispatch_queue_create("Kenneth", nil)
现在我们直接使用 init 函数:
let queue = DispatchQueue(label: "Kenneth")
这么写来创建一个并行队列:
let conQueue = DispatchQueue(label: "Kenneth", attributes: .concurrent)
任务
像之前所说的一样,在 Swift 3 中指派任务更加自然,也更加方便。你只需要在你所指定的队列后使用相应的方法(.sync、.async),然后使用闭包传入任务即可。
同步任务
使用 .sync 方法,例如:
let queue = DispatchQueue(label: "Kenneth")
queue.sync {
print("Hello World")
}
Tips:请避免在主线程指定同步任务,否则你的主线程可能会锁死。
异步任务
使用 .async 方法,例如:
let queue = DispatchQueue(label: "Kenneth")
queue.async {
print("Hello World")
}
延时执行
之前我们在 GCD 中,想要指派一个任务延时执行(比如等待一个动画),需要写的代码十分复杂。我们来看看 Swift 3 中是怎样的:
let delay = DispatchTime.now() + 3.5
DispatchQueue.main.asyncAfter(deadline: delay) {
// 你想做啥
}
Boom!
Tips:要注意这里的单位是秒(s),如果你想更改单位的话,可以使用 DispatchTime 的 enum:
- .seconds(Int)
- .milliseconds(Int)
- .microseconds(Int)
- .nanoseconds(Int)
dispatch_once
dispatch_once 在 OC 时代是一个用来写单例的很好的工具,它保证任务只执行一次。然而在 Swift 3 中,这个函数被删除了。
在 Swift 中,好用又简洁的单例请使用 static let。
总结
随着 Swift 3 的正式发布,GCD 新型的使用方式将会被越多的人所认识。作为 iOS 开发者,我很乐于见到 GCD 新的语法更加现代化,更符合面向对象的思想,也更便于使用。下面是常用的 GCD 模板在 Swift 3 中的写法,供大家参考。
全局队列异步
DispatchQueue.global().async {
//Something that wastes time
DispatchQueue.main.async {
//返回主线程
}
}
延时操作,注意这里的单位是秒
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3.5) {
// 你想做啥
}
创建队列同步
let queue = DispatchQueue(label: "Kenneth")
queue.sync {
print("Hello World")
}