Swift 中的 Operation 和 OperationQueue 如何使用

Operation 在 Swift 中是一种非常好用的类,它可以根据职责将业务拆分成多个类,保持代码的易读性。同时它还可以跟踪任务执行进度和控制任务依赖关系。

它和 ObjC 中 NSOperation 是一样的,一般结合 OperationQueue 来使用。

Operation 详解

在日常开发中,一个 Operation 的实例通常负责单个同步任务。需要注意的是:Operation 是一个抽象类,我们不应该直接使用它。而是应该使用系统实现的子类 BlockOperation 或者自己集成它,来自定义实现一个子类。

Operation 的任务启动方式有两种:

  • 将其添加到 OperationQueue
  • 手动调用其 start 方法启动

推荐 由 OperationQueue 来管理状态。

使用系统定义的 BlockOperation 代码示例如下:

scss 复制代码
let blockOperation = BlockOperation { 
    print("执行任务")
} 
let queue = OperationQueue() queue.addOperation(blockOperation)

当然,也可以直接调用 OperationQueue 的方法来添加任务:

arduino 复制代码
queue.addOperation { 
    print("执行任务")
}

给定的任务被添加到 OperationQueue中,它将尽快开始执行。

如何自定义 Operation 的子类

你可以通过自定义实现其子类,来进行业务代码的解耦。比如你有一个需求:导入用户的数据并将其上传到服务器。你可以实现一个负责导入的 Operation、再创建一个负责上传的 Operation。

下面的代码示例展示了一个用于导入内容的自定义子类:

swift 复制代码
final class ContentImportOperation: Operation {

    let itemProvider: NSItemProvider

    init(itemProvider: NSItemProvider) {
        self.itemProvider = itemProvider
        super.init()
    }

    override func main() {
        guard !isCancelled else { return }
        print("数据正在导入")
    }根据操作当前的执行状态,操作可以处于几种状态
}

该类接受一个 itemProvider 的参数来接受导入的内容,并在 main 方法中将其导入。main 方法是唯一需要覆盖同步操作的方法。

将操作添加到队列中,并设置一个 block 来跟踪完成:

ini 复制代码
let fileURL = URL(fileURLWithPath: "xxxx")
let contentImportOperation = ContentImportOperation(itemProvider: NSItemProvider(contentsOf: fileURL)!)

contentImportOperation.completionBlock = {
    print("导入完成")
}

queue.addOperation(contentImportOperation)

通过自定义 ContentImportOperation,我们可以将导入内容相关的逻辑都放在其中,在该类中跟踪任务进度和完成情况,这样代码逻辑会很清晰,并且测试代码也非常好写。

Operation 的几种状态

根据 Operation 当前的执行状态,其可以处于几种状态:

  • Ready:准备好开始任务
  • Executing:任务正在执行中
  • Finished:任务已经完成
  • Canceled:任务已被取消

需要注意的是,一个 Operation 只能执行一次。无论何时它处于完成或取消状态,你都不能再重新启动同一个实例。

在自定义实现中,你需要在执行之前手动检查取消状态,以确保任务被取消。还有就是,当 Operation 在同一时间开始和取消时,可能会发生数据竞争的问题。

一旦任务完成,OperationQueue 就会自动从队列中删除任务,不仅仅只有执行完成才会删除任务,取消任务也会触发。

管理依赖

使用 Operation 的一个很大的好处是使用依赖关系。你可以轻松地在两个实例之间添加依赖项。例如:导入内容后开始上传:

ini 复制代码
let fileURL = URL(fileURLWithPath: "xxx")
let contentImportOperation = ContentImportOperation(itemProvider: NSItemProvider(contentsOf: fileURL)!)
contentImportOperation.completionBlock = {
    print("导入完成")
}

let contentUploadOperation = UploadContentOperation()
contentUploadOperation.addDependency(contentImportOperation)
contentUploadOperation.completionBlock = {
    print("上传完成")
}

queue.addOperations([contentImportOperation, contentUploadOperation], waitUntilFinished: true)

只有在内容导入完成后,上传任务才会开始执行。需要说一下的是:如果导入操作取消,上传仍将开始。因为对于 Operation 来说它并不关心任务是执行导致完成还是取消导致的完成。

所以如果你想取消导入并不触发上传的话,你还需要在上传中添加判断逻辑:

swift 复制代码
final class UploadContentOperation: Operation {
    override func main() {
        guard !dependencies.contains(where: { $0.isCancelled }), !isCancelled else {
            return
        }

        print("上传中")
    }
}
相关推荐
tangweiguo0305198714 小时前
SwiftUI布局完全指南:从入门到精通
ios·swift
T1an-118 小时前
最右IOS岗一面
ios
坏小虎21 小时前
Expo 快速创建 Android/iOS 应用开发指南
android·ios·rn·expo
光影少年1 天前
Android和iOS原生开发的基础知识对RN开发的重要性,RN打包发布时原生端需要做哪些配置?
android·前端·react native·react.js·ios
北京自在科技1 天前
Find My 修复定位 BUG,AirTag 安全再升级
ios·findmy·airtag
Digitally1 天前
如何不用 USB 线将 iPhone 照片传到电脑?
ios·电脑·iphone
Sim14801 天前
iPhone将内置本地大模型,手机端AI实现0 token成本时代来临?
人工智能·ios·智能手机·iphone
Digitally2 天前
如何将 iPad 上的照片传输到 U 盘(4 种解决方案)
ios·ipad
报错小能手2 天前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift
LcGero2 天前
Cocos Creator 业务与原生通信详解
android·ios·cocos creator·游戏开发·jsb