引言
-
Operation Queue 概述
Cocoa 操作是一种面向对象的方式,用于封装需要异步执行的工作。操作可以单独使用,也可以与操作队列结合使用。
-
基础概念
-
NSOperation
它是一个抽象类,用于定义需要执行的任务。你可以子类化或者使用它的系统子类BlockOpeation
-
自定义NSOperation
// 继承Operation class MyOperation: Operation { override func main() { Thread.sleep(forTimeInterval: 2.0) print("do something (Thread.current)") } } let op = MyOperation() // 不会开启线程,如果operation被添加到Operation Queue, 手动调用`start()`会导致异常 op.start() -
NSBlockOperation
let operation = BlockOperation { print("do something") } // 不会开启线程,如果operation被添加到Operation Queue, 手动调用`start()`会导致异常 operation.start()
-
Operation Queues
NSOperationQueue 是一个高效的队列,专门用于管理NSOperation对象。它负责操作的执行顺序,并提供了并发执行的能力
class MyOperation: Operation { override func main() { Thread.sleep(forTimeInterval: 2.0) print("op1: do something (Thread.current)") } } let op1 = MyOperation() let op2 = BlockOperation { print("op2: do something (Thread.current)") } let op3 = BlockOperation { print("op3: do something (Thread.current)") } let queue = OperationQueue() // 设置最大并发数,当最大并发数是1 时,此时和Dispatch串行队列类似 queue.maxConcurrentOperationCount = 6 //设置依赖管理 执行书序 op1 -> op2 -> op3 op2.addDependency(op1) op3.addDependency(op2) // 添加到Operation Queue,由Operation Queue 调度 queue.addOperation(op1) queue.addOperation(op2) queue.addOperation(op3) // 取消操作不会自动将它们从队列中移除,也不会停止那些正在执行的操作。对于已排队且等待执行的操作,队列仍然会尝试执行该操作,然后才会识别到它已被取消并将其移至完成状态。对于那些已经在执行的操作,操作对象本身必须检查是否已被取消,并停止正在进行的工作,以便能移至完成状态。在这两种情况下,已完成(或取消)的操作仍有机会执行其完成块(completion block),然后才会从队列中移除。 queue.cancelAllOperations() -
Operation 的状态
isReady操作是否已准备好执行isExecuting操作是否正在执行isFinished操作是否已完成isCancelled操作是否已经取消
注意我们一般会在Operation main()函数中检查状态isCancelled,如果main()函数中有多个耗时子任务,我们可以多次检查这个状态,以便及时退出,减少资源占用。 -
取消任务
有两种方式可以取消任务。
- 调用opeation的
cancel()函数 - 调用operationQueue的
cancelAllOperations函数, 他会对队列中所有Opeation 调用cancel()函数.注意: 对于那些正在等待执行的操作,队列仍然尝试执行操作,然后识别到取消状态,将它变为已完成状态。对于已经执行的操作,操作对象必须检查取消状态,并停止正在进行的工作,以便可以移到已完成状态。
-
实战
自定义两个Operation, Download Operation 负责下载图片并存入磁盘,Process Operation 负责读取图片并处理图片,处理完成时展示图片。Process Operation 依赖 Download Operation。
注意:completionBlock 只是任务完成的一个通知,可以在闭包里更新UI, 但是不能在里面给别的Operation 传递参数,因为此时下一个Opeation 可能已经开始执行。 比如在这个例子中不能在download operation 的completionBlock 给 processOperation 传递参数。
@IBAction func startDownload(_ sender: NSButton) {
imageView1.image = nil
let imageURL =
URL(string: "https://cdn.arstechnica.net/wp-content/uploads/2018/06/macOS-Mojave-Dynamic-Wallpaper-transition.jpg")!
let downloadOperation = DownloadImageOperation(imageURL: imageURL, diskPath: pathSaveToDisk)
let processOperation = ProcessImageOperation(diskPath: pathSaveToDisk)
// 处理图片完成后更新UI
processOperation.completionBlock = {
// 主队列更新UI
DispatchQueue.main.async {
self.imageView1.image = processOperation.outputImage
}
}
// 添加依赖,图片下载到磁盘后,读取磁盘的图片并处理
processOperation.addDependency(downloadOperation)
operationQueues.addOperation(downloadOperation)
operationQueues.addOperation(processOperation)
}
class ProcessImageOperation: Operation {
let diskPath: String
var outputImage: NSImage?
init(diskPath: String) {
self.diskPath = diskPath
}
override func main() {
// 检查是否已取消
guard !isCancelled else { return }
guard let imageToBeProcessed = CIImage(contentsOf: URL(fileURLWithPath: diskPath)) else { return }
outputImage = applyFilter(to: imageToBeProcessed)
}
private func applyFilter(to image: CIImage) -> NSImage? {
let filter = CIFilter(name: "CIFalseColor")!
filter.setValue(image, forKey: kCIInputImageKey)
filter.setValue(CIColor(red: 0.0, green: 0.0, blue: 1.0), forKey: "inputColor0")
filter.setValue(CIColor(red: 0.0, green: 1.0, blue: 0.0), forKey: "inputColor1")
guard let effectImage = filter.outputImage else { return nil }
let context = CIContext()
guard let cgImage = context.createCGImage(effectImage, from: effectImage.extent) else { return nil }
let nsImage = NSImage(cgImage: cgImage, size: effectImage.extent.size)
return nsImage
}
}
class DownloadImageOperation: Operation {
var imageURL: URL
let diskPath: String
init(imageURL: URL, diskPath: String) {
self.imageURL = imageURL
self.diskPath = diskPath
}
override func main() {
// 检查是否已取消
guard !isCancelled else { return }
// 下载图片
guard let downloadedData = try? Data(contentsOf: imageURL) else { return }
// 存储图片到磁盘
do {
try downloadedData.write(to: URL(fileURLWithPath: diskPath))
} catch {
print("Failed to write image to disk: \(error)")
}
}
}
结语
希望通过这篇文章的介绍,大家对 Operation Queue 有了更全面的了解,并能在实际项目中有效的应用这一技术,从而打造更加高效和稳定的程序。
感谢阅读,如果你有任何疑问或者建议,欢迎在评论区留言交流!