尝试理解异步操作子类

Trying to Understand Asynchronous Operation Subclass

我正尝试开始在副项目中使用 Operations,而不是在我的网络代码中散布基于闭包的回调,以帮助消除嵌套调用。所以我正在阅读有关该主题的一些内容,并且遇到了 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 subclass 有一些实现细节,我希望对理解有所帮助。

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

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

关于您的第一个问题:stateQueue 在向您的操作状态写入新值时通过以下方式锁定您的操作:

    return stateQueue.sync(execute: {
            rawState
    })

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

由于您的操作是异步的,因此在读取或写入一个状态之前可以调用另一个状态。就像你想写 isExecution 但同时 isFinished 已经被调用了。因此,为了避免这种情况,stateQueue 锁定要读写的操作状态,直到它完成之前的调用。它的工作方式类似于 Atomic。而是使用调度队列,您可以使用 NSLock 的扩展来简化从 WWDC 2015 https://developer.apple.com/videos/play/wwdc2015/226/ from https://developer.apple.com/sample-code/wwdc/2015/downloads/Advanced-NSOperations.zip 中的 Advanced 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")
    }
}

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

你说:

  1. What is the purpose of the stateQueue property? I see it being used by get and set of the state computed property, but I can't find any documentation that explains the sync:flags:execute and sync:execute methods that they use.

此代码“同步”对 属性 的访问以使其线程安全。关于为什么需要这样做,请参阅 the Operation documentation,其中建议:

Multicore Considerations

... When you subclass NSOperation, you must make sure that any overridden methods remain safe to call from multiple threads. If you implement custom methods in your subclass, such as custom data accessors, you must also make sure those methods are thread-safe. Thus, access to any data variables in the operation must be synchronized to prevent potential data corruption. For more information about synchronization, see Threading Programming Guide.

关于此并发队列用于同步的确切用途,这称为“reader-writer”模式。 reader-writer 模式的这个基本概念是读取可以相互并发发生(因此 sync,没有障碍),但写入绝不能与任何其他访问并发执行属性(因此 async 有屏障)。

例如,您可以像这样在数组上实现一个 reader-writer 以实现线程安全:

class ThreadSafeArray<T> {
    private var values: [T]
    private let queue = DispatchQueue(label: "...", attributes: .concurrent)
    
    init(_ values: [T]) {
        self.values = values
    }
    
    func reader<U>(block: () throws -> U) rethrows -> U {
        return try queue.sync {
            try block()
        }
    }
    
    func writer(block: @escaping (inout [T]) -> Void) {
        queue.async(flags: .barrier) {
            block(&self.values)
        }
    }
    
    // e.g. you might use `reader` and `writer` like the following:
    
    subscript(_ index: Int) -> T {
        get { reader { values[index] } }
        set { writer { [=10=][index] = newValue } }
    }
    
    func append(_ value: T) {
        writer { [=10=].append(value) }
    }
    
    func remove(at index: Int) {
        writer { [=10=].remove(at: index)}
    }
}

显然,在这个 Operation subclass 中使用 reader-writer 更简单,但上面说明了模式。

您还问过:

  1. What is the purpose of the three class methods in the NSObject section that return ["state"]? I don't see them being used anywhere. I found, in NSObject, class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String>, but that doesn't seem to help me understand why these methods are declared.

这些只是确保对 state 属性 的更改触发属性 isReady, isExecuting and isFinished. The KVO notifications of these three keys is critical for the correct functioning of asynchronous operations. Anyway, this syntax is outlined in the Key-Value Observing Programming Guide: Registering Dependent Keys.

的 KVO 通知的方法

您找到的keyPathsForValuesAffectingValue方法是相关的。您可以使用该方法注册依赖键,也可以使用原始代码片段中显示的各个方法。


顺便说一句,这里是你提供的AsynchronousOperationclass的修改版,即:

  1. 你不能打电话给super.start()。正如 start documentation 所说(强调):

    If you are implementing a concurrent operation, you must override this method and use it to initiate your operation. Your custom implementation must not call super at any time.

  2. 在Swift中添加@objc 4.

  3. 重命名 execute 以使用 main,这是 Operation subclasses 的约定。

  4. isReady声明为final属性是不合适的。任何 subclass 都应该有权进一步完善其 isReady 逻辑(尽管我们承认很少这样做)。

  5. 使用#keyPath让代码多一点safe/robust.

  6. 使用 dynamic 属性 时,您不需要手动 KVO 通知。本例不需要手动调用willChangeValuedidChangeValue

  7. 更改 finish 以便它仅在尚未完成时移动到 .finished 状态。

因此:

public class AsynchronousOperation: Operation {
    
    /// State for this operation.
    
    @objc private enum OperationState: Int {
        case ready
        case executing
        case finished
    }
    
    /// Concurrent queue for synchronizing access to `state`.
    
    private let stateQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".rw.state", attributes: .concurrent)
    
    /// Private backing stored property for `state`.
    
    private var _state: OperationState = .ready
    
    /// The state of the operation
    
    @objc private dynamic var state: OperationState {
        get { return stateQueue.sync { _state } }
        set { stateQueue.async(flags: .barrier) { self._state = newValue } }
    }
    
    // MARK: - Various `Operation` properties
    
    open         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 }

    // KVN for dependent properties
    
    open override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
        if ["isReady", "isFinished", "isExecuting"].contains(key) {
            return [#keyPath(state)]
        }
        
        return super.keyPathsForValuesAffectingValue(forKey: key)
    }
    
    // Start
    
    public final override func start() {
        if isCancelled {
            state = .finished
            return
        }
        
        state = .executing
        
        main()
    }
    
    /// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception.
    
    open override func main() {
        fatalError("Subclasses must implement `main`.")
    }
    
    /// Call this function to finish an operation that is currently executing
    
    public final func finish() {
        if !isFinished { state = .finished }
    }
}

使用来自 的更新代码片段时,应注意此更改可能导致的错误:

  1. Change finish so that it only moves to .finished state if isExecuting.

以上内容不利于 Apple docs:

In addition to simply exiting when an operation is cancelled, it is also important that you move a cancelled operation to the appropriate final state. Specifically, if you manage the values for the finished and executing properties yourself (perhaps because you are implementing a concurrent operation), you must update those properties accordingly. Specifically, you must change the value returned by finished to YES and the value returned by executing to NO. You must make these changes even if the operation was cancelled before it started executing.

这在少数情况下会导致错误。例如,如果 Operation Queue with "maxConcurrentOperationCount = 1" 得到 3 个异步操作 A B 和 C,那么如果在 A 期间取消所有操作,C 将不会被执行并且队列将卡在操作 B.