Operation Queue 技术实践

406 阅读4分钟

引言

  • Operation Queue 概述

    Cocoa 操作是一种面向对象的方式,用于封装需要异步执行的工作。操作可以单独使用,也可以与操作队列结合使用。

  • 基础概念

    • NSOperation

    它是一个抽象类,用于定义需要执行的任务。你可以子类化或者使用它的系统子类BlockOpeation

    1. 自定义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()
      
    2. 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()函数中有多个耗时子任务,我们可以多次检查这个状态,以便及时退出,减少资源占用。

    • 取消任务

    有两种方式可以取消任务。

    1. 调用opeation的cancel()函数
    2. 调用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 有了更全面的了解,并能在实际项目中有效的应用这一技术,从而打造更加高效和稳定的程序。

感谢阅读,如果你有任何疑问或者建议,欢迎在评论区留言交流!