使用异步 Operation,我们可以在其内部执行耗时任务而不阻塞主线程 UI。而且 Operation 的设计也可以使我们很好的根据业务拆分代码,使代码更具有可读性,更好维护。而且使用它也可以使我们很容易的实现并行任务的依赖关系。
异步(Asynchronous) VS 同步(Synchronous)Operation
虽然从英文单词上来说,它们只是一个字母的区别。但实际的差异要大得多。同步 Operation 的创建和使用要容易得多,但它不适合执行耗时任务,这会导致线程阻塞,给用户带来不好的体验。
而异步 Operation 则可以使你执行耗时任务而不阻塞线程。它主要可以做以下几件事情:
- 运行耗时任务
- 将 Operation 派发到另一个队列
- 可以无风险的手动调用 start
无论同步还是异步,都可以手动启动。手动启动的意思就是:手动调用 start()
方法,而不是使用 OperationQueue
来管理任务的执行。同步操作在启动之后,直至它完成之前,都会阻塞调用线程。因此,它们不太适合手动启动。但使用异步任务时,阻塞调用线程的风险就不那么重要了,因为它很可能被分派到另一个线程。
尽管现在手动启动异步任务不会造成什么风险,但还是不建议这样做。通过使用 OperationQueue
,你不必考虑多个操作的执行顺序,并且还可以享受任务优先级等更多特性。因此,建议应该总是通过操作添加到 OperationQueue
来启动。
创建一个异步 Operation
创建异步 Operation 首先要创建继承自 Operation
的自定义子类并重写 isAsynchronous
属性:
class AsyncOperation: Operation {
override var isAsynchronous: Bool {
return true
}
override func main() {
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1), execute: {
print("执行异步操作")
})
}
}
这还不足以使任务异步执行,因为任务在执行 print
语句后会直接进入完成状态。这可以通过执行下面的代码来演示:
let operation = AsyncOperation()
queue.addOperations([operation], waitUntilFinished: true)
print("任务执行完成")
// Prints:
// 任务执行完成
// 执行异步操作
换句话说,当异步任务仍在执行时,任务已经标记为完成,这会导致意想不到的行为。所以我们需要自己来管理状态,以使 Operation 能正常的异步工作。
管理异步 Operation 的状态
为了正确地管理状态,我们需要用多线程和 KVO
来实现重写 isFinished
和 isExecuting
属性。代码如下:
class AsyncOperation: Operation {
private let lockQueue = DispatchQueue(label: "com.test.asyncoperation", attributes: .concurrent)
override var isAsynchronous: Bool {
return true
}
private var _isExecuting: Bool = false
override private(set) var isExecuting: Bool {
get {
return lockQueue.sync { () -> Bool in
return _isExecuting
}
}
set {
willChangeValue(forKey: "isExecuting")
lockQueue.sync(flags: [.barrier]) {
_isExecuting = newValue
}
didChangeValue(forKey: "isExecuting")
}
}
private var _isFinished: Bool = false
override private(set) var isFinished: Bool {
get {
return lockQueue.sync { () -> Bool in
return _isFinished
}
}
set {
willChangeValue(forKey: "isFinished")
lockQueue.sync(flags: [.barrier]) {
_isFinished = newValue
}
didChangeValue(forKey: "isFinished")
}
}
override func start() {
print("开始执行任务")
guard !isCancelled else {
finish()
return
}
isFinished = false
isExecuting = true
main()
}
override func main() {
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1), execute: {
print("任务执行中")
self.finish()
})
}
func finish() {
isExecuting = false
isFinished = true
}
}
为了确保我们的任务能够正常工作,我们可以执行之前的测试代码:
let operation = AsyncOperation()
queue.addOperations([operation], waitUntilFinished: true)
print("任务执行完成")
// Prints:
// 开始执行任务
// 任务执行完成
// 执行异步操作
从打印可以看出,它可以正常工作了。