Swift多线程
本文介绍三种不同的多线程技术,分别是Thread、Grand Central Dispatch(GCD)和Operation。
Thread
Thread在三种多线程技术中是最轻量级的,但需要自己来管理线程的生命周期和同步问题。Thread有两种创建方式:
- 通过工厂方法直接创建线程并自动运行
- 先创建一个线程对象,然后手动运行线程
1 | class ThreadTest { |
Grand Central Dispatch(GCD)
GCD的基本概念是将任务添加到调度队列中,队列中的任务稍后会以先到先执行的顺序执行。任务分为同步任务和异步任务,同步任务会阻塞当前线程,直到任务完成。异步任务不会等待任务执行结束,而是马上返回到当前线程继续执行下一个任务。队列也分为串行队列和并发队列,串行队列在任何时刻都只有一个任务被执行,并发队列可能会有多个任务并发执行,这依赖于系统环境。
队列
使用DispatchQueue
来新建调度队列,新建后系统会为该队列分配线程。
1
2
3
4
5
6// 使用独一无二的label参数来表示队列意图
// 默认创建的是串行队列
let queue = DispatchQueue(label: "com.gcfeng.networking")
// 如果要创建并发队列,可以传递attributes参数
let queue = DispatchQueue(label: "com.gcfeng.networking", attributes: .concurrent)DispatchQueue.main
可以访问该主队列。需要注意的是不要在主队列中执行同步任务,除非这些任务是更新UI的,否则可能会造成界面卡顿。
任务有优先级,优先级高的会先执行,分配有更多资源,但是也希望执行时间尽可能快。系统使用QoS(Quality of Service)来描述优先级,共定义了6种不同的QoS:
- .userInteractive:用户交互的任务,比如动画或任何希望UI快速更新的任务。优先级最高
- .userInitiated:需要马上获取结果,比如打开一个文档或者从本地数据库中读取记录
- .utility:可以执行很长时间,然后通知用户结果。比如文件下载,给用户下载进度
- .background:耗时较久的任务,且用户并不关心完成时间。比如后台数据同步等
- .default和.unspecified:.default是默认值,介于.userInitiated和.userInteractive之间。.unspecified是为了兼容旧API的。这两个值都不推荐直接使用。
1 | // 在创建队列的时候,可以传递qos参数来指定队列的优先级 |
任务
任务是一段闭包代码。队列使用sync来创建同步任务,使用async来创建异步任务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 创建了异步任务
DispatchQueue.global(qos: .utility).async { [weak self] in
// [weak self]表示我们弱引用self变量,那么在使用self变量之前需要进行检查
// 实际上GCD的异步任务不会造成循环引用,所以这里是否使用弱引用因需求而定
guard let self = self else { return }
// 执行任务代码
// ...
// 回到主线程来更新UI
DispatchQueue.main.async {
self.textLabel.text = "New post available!"
}
}
组
可以将队列中的任务分组,当组内所有任务都完成时发出通知。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class GCDTest {
func run() {
// 新建一个队列
let queue = DispatchQueue(label: "com.gcfeng.test", attributes: .concurrent)
// 新建一个组
let group = DispatchGroup()
queue.async(group: group) {
for i in 0..<5 {
print("task1: \(i)")
}
}
queue.async(group: group) {
for i in 0..<5 {
print("task2: \(i)")
}
}
// 当组内的任务全部完成时,通知主线程
group.notify(queue: DispatchQueue.main) {
print("All task finish")
}
}
}
let gcdTest = GCDTest()
gcdTest.run()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20func asyncAdd(lhs: Int, rhs: Int, completion: @escaping (Int) -> Void) {
// 一些代码
// 完成后执行逃逸闭包
completion(lhs + rhs)
}
func asyncAddForGroup(
group: DispatchGroup,
lhs: Int,
rhs: Int,
completion: @escaping (Int) -> Void) {
// 表示开始进入异步方法
group.enter()
asyncAdd(lhs: lhs, rhs: rhs) { result in
// 确保一定会进入leave,通知异步方法执行结束
defer { group.leave() }
completion(result)
}
}
信号量
信号量可以控制资源的并发访问。以下代码会顺序输出0到9:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15func run() {
// 创建信号量,1表示最多只能1个线程访问资源
let semaphore = DispatchSemaphore(value: 1)
let queue = DispatchQueue.global()
for i in 0..<10 {
queue.async {
// 等待资源释放
semaphore.wait()
print(i)
// 任务结束,可以执行下一个等待任务
semaphore.signal()
}
}
}
run()
Operation
Operation构建于GCD之上,提供了operation依赖、取消正在执行的operation等功能。每个operation有以下几种状态:
- isReady:实例化完成,准备执行
- isExecuting:当调用start方法,operation进入该状态
- isCancelled:当调用cancel方法,operation进入该状态
- isFinished:如果operation正常结束,进入该状态
两种创建方式
1. BlockOperation
可以使用封装好的BlockOperation来新建operation。BlockOperation类似DispatchGroup,可以添加多个任务,每个任务会被并发执行。
1
2
3
4
5
6
7
8
9
10
11
12
13let sentence = "Say hello to gcfeng"
let wordOperation = BlockOperation()
for word in sentence.split(separator: " ") {
wordOperation.addExecutionBlock {
print(word)
}
}
// 当所有任务结束之后,执行
wordOperation.completionBlock = {
print("All word printed")
}
// 启动
wordOperation.start()
创建Operation子类对象,然后将对象添加到OperationQueue队列中,一旦对象被加入到队列中,队列就开始处理该对象,直到对象的所有操作都执行完成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Test {
func run() {
// 创建Operation
let downloadOperation = DownloadOperation()
// 创建队列
let queue = OperationQueue()
queue.addOperation(downloadOperation)
}
}
class DownloadOperation: Operation {
override func main() {
print("download")
}
}
Test().run()
管理依赖
Operation的优势在于封装了Operation之间的依赖管理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class DownloadOperation: Operation {
override func main() {
print("download")
}
}
class UploadOperation: Operation {
override func main() {
// 检查依赖是否被取消或者自己被取消了
guard !dependencies.contains(where: { $0.isCancelled }), !isCancelled else {
return
}
print("upload")
}
}
// 实例化两个Operation
let downloadOperation = DownloadOperation()
let uploadOperation = UploadOperation()
// 定义uploadOperation依赖于downloadOperation
// 那么uploadOperation需要等待downloadOperation完成
uploadOperation.addDependency(downloadOperation)
uploadOperation.completionBlock = {
print("upload complete")
}
// 将Operation都添加到OperationQueue中
let queue = OperationQueue()
queue.addOperations([downloadOperation, uploadOperation], waitUntilFinished: false)