试图理解异步操作子类

时间:2017-04-22 15:42:40

标签: swift nsoperation

我正在尝试在一个辅助项目中使用Operation,而不是在我的网络代码中散布基于闭包的回调以帮助消除嵌套调用。所以我正在阅读这个主题,我遇到了this实现:

open class AsynchronousOperation: Operation {

    // MARK: - Properties

    private let stateQueue = DispatchQueue(label: "asynchronous.operation.state", attributes: .concurrent)

    private var rawState = OperationState.ready

    private dynamic var state: OperationState {
        get {
            return stateQueue.sync(execute: {
                rawState
            })
        }
        set {
            willChangeValue(forKey: "state")
            stateQueue.sync(flags: .barrier, execute: {
                rawState = newValue
            })
            didChangeValue(forKey: "state")
        }
    }

    public final override var isReady: Bool {
        return state == .ready && super.isReady
    }

    public final override var isExecuting: Bool {
        return state == .executing
    }

    public final override var isFinished: Bool {
        return state == .finished
    }

    public final override var isAsynchronous: Bool {
        return true
    }


    // MARK: - NSObject

    private dynamic class func keyPathsForValuesAffectingIsReady() -> Set<String> {
        return ["state"]
    }

    private dynamic class func keyPathsForValuesAffectingIsExecuting() -> Set<String> {
        return ["state"]
    }

    private dynamic class func keyPathsForValuesAffectingIsFinished() -> Set<String> {
        return ["state"]
    }


    // MARK: - Foundation.Operation

    public final override func start() {
        super.start()

        if isCancelled {
            finish()
            return
        }

        state = .executing
        execute()
    }


    // MARK: - Public

    /// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception.
    open func execute() {
        fatalError("Subclasses must implement `execute`.")
    }

    /// Call this function after any work is done or after a call to `cancel()` to move the operation into a completed state.
    public final func finish() {
        state = .finished
    }
}

@objc private enum OperationState: Int {

    case ready

    case executing

    case finished
}

这个Operation子类的一些实现细节我想帮助理解。

  1. stateQueue属性的目的是什么?我看到它被get计算属性的setstate使用,但我找不到任何解释sync:flags:executesync:execute方法的文档他们使用。

  2. NSObject部分中返回["state"]的三个类方法的目的是什么?我没有看到它们被用在任何地方。我在NSObjectclass func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String>找到了,但这似乎并不能帮助我理解为什么会声明这些方法。

3 个答案:

答案 0 :(得分:34)

你说:

  
      
  1. stateQueue财产的目的是什么?我看到它被state计算属性的get和set使用,但我找不到任何解释他们使用的sync:flags:executesync:execute方法的文档。
  2.   

此代码&#34;同步&#34;访问属性以使其线程安全。关于你为什么需要这样做,请参阅the Operation documentation,建议:

  

多核注意事项

     

...当你继承NSOperation时,必须确保所有被覆盖的方法都可以安全地从多个线程调用。如果在子类中实现自定义方法(如自定义数据访问器),则还必须确保这些方法是线程安全的。因此,必须同步对操作中的任何数据变量的访问,以防止潜在的数据损坏。有关同步的更多信息,请参阅Threading Programming Guide

关于这个并发队列用于同步的确切用法,这被称为&#34;读写器&#34;图案。读写器模式的这个基本概念是读取可以相互发生并发生(因此sync,没有障碍),但写入必须永远不会同时执行该属性的任何其他访问(因此带障碍的async。这些都在WWDC 2012视频Asynchronous Design Patterns with Blocks, GCD, and XPC中有所描述。请注意,虽然该视频概述了基本概念,但它使用较旧的dispatch_syncdispatch_barrier_async语法,而不是使用仅syncasync(flags: .barrier)语法的Swift 3及更高版本语法这里。

您还问:

  
      
  1. 返回NSObject的{​​{1}}部分中三个类方法的目的是什么?我没有看到它们被用在任何地方。我在["state"]NSObject找到了,但这似乎无法帮助我理解为什么声明这些方法。
  2.   

这些只是确保对class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String>属性的更改触发属性isReadyisExecutingisFinished的KVN的方法。这三个密钥的KVN对于异步操作的正确运行至关重要。无论如何,Key-Value Observing Programming Guide: Registering Dependent Keys中概述了这种语法。

您找到的state方法是相关的。您可以使用该方法注册相关密钥,也可以使用原始代码段中显示的各个方法。

BTW,这是您提供的keyPathsForValuesAffectingValue课程的修订版,即:

  1. 您不得致电AsynchronousOperation。正如start documentation所说(强调补充):

      

    如果要实现并发操作,则必须覆盖此方法并使用它来启动操作。 您的自定义实施不得随时致电super.start()

  2. 在Swift 4中添加super

  3. 重命名@objc以使用execute,这是main子类的惯例。

  4. Operation声明为isReady属性是不合适的。任何子类都应该有权进一步细化其final逻辑(尽管我们很少这样做)。

  5. 使用isReady使代码更安全/更健壮。

  6. 使用#keyPath属性时,您不需要手动操作KVN。在此示例中,不需要手动调用dynamicwillChangeValue

  7. 更改didChangeValue,使其仅finish移至.finished州。

  8. 因此:

    isExecuting

答案 1 :(得分:2)

在使用Rob's answer中更新的代码段时,应该意识到由此更改引起的错误的可能性:

  
      
  1. 更改完成,使其仅在isExecuting时才进入.finished状态。
  2.   

以上内容与Apple docs不符:

  

除了在取消操作时简单退出之外,将取消的操作移至适当的最终状态也很重要。具体来说,如果您自己管理完成属性和执行属性的值(可能是因为您正在执行并发操作),则必须相应地更新这些属性。具体来说,您必须将finish返回的值更改为YES,将执行返回的值更改为NO。即使必须先取消操作,也必须进行这些更改。

在某些情况下会导致错误。例如,如果“ maxConcurrentOperationCount = 1”的操作队列获得3个异步操作A B和C,则如果在A期间取消所有操作,则C将不会执行,并且队列将卡在操作B上。

答案 2 :(得分:1)

关于你的第一个问题:stateQueue在你的操作状态写入新值时锁定你的操作:

    return stateQueue.sync(execute: {
            rawState
    })

    stateQueue.sync(flags: .barrier, execute: {
        rawState = newValue
    })

由于您的操作是异步的,因此在读取或写入一个状态之前,可以调用另一个状态。就像你想写的是执行,但同时已经完成了已完成的调用。因此,要避免这种情况,stateQueue会将操作状态锁定为读取和写入,直到完成其上一次调用。它的工作像Atomic。而是使用调度队列,您可以使用NSLock的扩展来简化从https://developer.apple.com/videos/play/wwdc2015/226/的WWDC 2015 https://developer.apple.com/sample-code/wwdc/2015/downloads/Advanced-NSOperations.zip中的高级NSOperations示例代码执行关键代码,您可以实现如下:

private let stateLock = NSLock()

private dynamic var state: OperationState {
    get {
        return stateLock.withCriticalScope{ rawState } 
    }
    set {
        willChangeValue(forKey: "state")

        stateLock.withCriticalScope { 
            rawState = newValue
        }
        didChangeValue(forKey: "state")
    }
}

关于你的第二个问题:它是一个只读属性isReady,isExecuting,isFinished的KVO通知来管理操作状态。您可以阅读:http://nshipster.com/key-value-observing发布到最后,以便更好地了解KVO。