使用 Swift 的异步 Operation 实现并发执行任务

使用异步 Operation,我们可以在其内部执行耗时任务而不阻塞主线程 UI。而且 Operation 的设计也可以使我们很好的根据业务拆分代码,使代码更具有可读性,更好维护。而且使用它也可以使我们很容易的实现并行任务的依赖关系。

异步(Asynchronous) VS 同步(Synchronous)Operation

虽然从英文单词上来说,它们只是一个字母的区别。但实际的差异要大得多。同步 Operation 的创建和使用要容易得多,但它不适合执行耗时任务,这会导致线程阻塞,给用户带来不好的体验。

而异步 Operation 则可以使你执行耗时任务而不阻塞线程。它主要可以做以下几件事情:

  • 运行耗时任务
  • 将 Operation 派发到另一个队列
  • 可以无风险的手动调用 start

无论同步还是异步,都可以手动启动。手动启动的意思就是:手动调用 start() 方法,而不是使用 OperationQueue 来管理任务的执行。同步操作在启动之后,直至它完成之前,都会阻塞调用线程。因此,它们不太适合手动启动。但使用异步任务时,阻塞调用线程的风险就不那么重要了,因为它很可能被分派到另一个线程。

尽管现在手动启动异步任务不会造成什么风险,但还是不建议这样做。通过使用 OperationQueue ,你不必考虑多个操作的执行顺序,并且还可以享受任务优先级等更多特性。因此,建议应该总是通过操作添加到 OperationQueue 来启动。

创建一个异步 Operation

创建异步 Operation 首先要创建继承自 Operation 的自定义子类并重写 isAsynchronous 属性:

csharp 复制代码
class AsyncOperation: Operation {
    override var isAsynchronous: Bool {
        return true
    }

    override func main() {
        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1), execute: {
            print("执行异步操作")
        })
    }
}

这还不足以使任务异步执行,因为任务在执行 print 语句后会直接进入完成状态。这可以通过执行下面的代码来演示:

scss 复制代码
let operation = AsyncOperation()
queue.addOperations([operation], waitUntilFinished: true)
print("任务执行完成")
// Prints:
// 任务执行完成
// 执行异步操作

换句话说,当异步任务仍在执行时,任务已经标记为完成,这会导致意想不到的行为。所以我们需要自己来管理状态,以使 Operation 能正常的异步工作。

管理异步 Operation 的状态

为了正确地管理状态,我们需要用多线程和 KVO 来实现重写 isFinished 和 isExecuting 属性。代码如下:

csharp 复制代码
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
    }
}

为了确保我们的任务能够正常工作,我们可以执行之前的测试代码:

scss 复制代码
let operation = AsyncOperation()
queue.addOperations([operation], waitUntilFinished: true)
print("任务执行完成")
// Prints:
// 开始执行任务
// 任务执行完成
// 执行异步操作

从打印可以看出,它可以正常工作了。

相关推荐
承渊政道11 小时前
iOS远程控制电脑用什么好?iPhoneiPad远程控制Windows电脑最佳方案2026横评
windows·ios·远程控制·1024程序员节·iphone ipad
茶底世界之下11 小时前
彩色建筑适合转黑白吗?用 Awaking 检查颜色消失后的层次
ios
黑科技iOS上架12 小时前
App Store 26年10月新应用审核放缓大量应用不进审
ios·审核
OxYGC13 小时前
手机为什么能“自动跳过“开屏广告?李跳跳、GKD 与 iOS 代理软件原理盘点
android·ios·开屏广告·广告屏蔽
终端安全笔记1 天前
一份配置管十项 AI 功能:它关的是入口,不是数据
网络安全·ios·移动设备管理
2501_915106321 天前
跨平台抓包工具怎么选?TraceEagle抓包鹰 的四种跨平台能力
网络协议·计算机网络·网络安全·ios·adb·https·udp
黑科技iOS上架1 天前
iOS应用上架:提前规避审核被拒风险
ios·审核
茶底世界之下1 天前
方图一定要裁照片吗?用 Awaking 比较裁切与留白
ios
00后程序员张2 天前
无需 Mac 的 iOS App 上架工具,开心上架Appuploader 负责哪些环节
android·ios·小程序·https·uni-app·iphone·webview
2501_915106322 天前
Windows 用 KXBuild 开发 iOS 项目,CocoaPods、SPM 怎么处理?
windows·vscode·ios·个人开发·swift·敏捷流程·cocoapods