试图理解异步操作子类

时间:2021-06-03 21:03:01

I am trying to get started with using Operations in a side project rather than having closure-based callbacks littered throughout my networking code to help eliminate nested calls. So I was doing some reading on the subject, and I came across this implementation:

我试图开始在副项目中使用Operations,而不是在我的网络代码中散布基于闭包的回调以帮助消除嵌套调用。所以我正在读一些关于这个主题的内容,我遇到了这个实现:

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
}

There are some implementation details of this Operation subclass that I would like some help in understanding.

这个Operation子类有一些实现细节,我想在理解上有所帮助。

  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.

    stateQueue属性的目的是什么?我看到它被状态计算属性的get和set使用,但我找不到任何解释sync的文档:flags:execute和sync:执行他们使用的方法。

  2. 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”]的NSObject部分中三个类方法的目的是什么?我没有看到它们被用在任何地方。我在NSObject中找到了类func keyPathsForValuesAffectingValue(forKey key:String) - > Set ,但这似乎没有帮助我理解为什么声明这些方法。

2 个解决方案

#1


18  

You said:

你说:

  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.
  2. stateQueue属性的目的是什么?我看到它被状态计算属性的get和set使用,但我找不到任何解释sync的文档:flags:execute和sync:执行他们使用的方法。

This code "synchronizes" access to a property to make it thread safe. Regarding why you need to do that, see the Operation documentation, which advises:

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

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.

...当您继承NSOperation时,必须确保从多个线程调用任何重写的方法都是安全的。如果在子类中实现自定义方法(例如自定义数据访问器),则还必须确保这些方法是线程安全的。因此,必须同步对操作中的任何数据变量的访问,以防止潜在的数据损坏。有关同步的更多信息,请参阅“线程编程指南”。

Regarding the exact use of this concurrent queue for synchronization, this is known as the "reader-writer" pattern. This basic concept of reader-writer pattern is that reads can happen concurrent with respect to each other (hence sync, with no barrier), but writes must never be performed concurrently with respect to any other access of that property (hence async with barrier). This is all described in WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC. Note, while that video outlines the basic concept, it uses the older dispatch_sync and dispatch_barrier_async syntax, rather than the Swift 3 and later syntax of just async and sync(flags: .barrier) syntax used here.

关于这个并发队列用于同步的确切用法,这被称为“读写器”模式。读写器模式的这个基本概念是读取可以相互发生并发(因此同步,没有障碍),但写入必须永远不会同时执行该属性的任何其他访问(因此与屏障异步) 。这在WWDC 2012视频异步设计模式与块,GCD和XPC中都有描述。请注意,虽然该视频概述了基本概念,但它使用较旧的dispatch_sync和dispatch_barrier_async语法,而不是此处使用的async和sync(flags:.barrier)语法的Swift 3及更高版本语法。

You also asked:

你还问:

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

These are just methods that ensure that changes to the state property trigger KVN for properties isReady, isExecuting and isFinished. The KVN 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.

这些只是确保对属性的更改触发属性isReady,isExecuting和isFinished的KVN的方法。这三个密钥的KVN对于异步操作的正确运行至关重要。无论如何,这个语法在Key-Value Observing Programming Guide:Registering Dependent Keys中有概述。

The keyPathsForValuesAffectingValue method you found is related. You can either register dependent keys using that method, or have the individual methods as shown in your original code snippet.

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


BTW, here is a revised version of the AsynchronousOperation class you provided, namely:

顺便说一句,这是您提供的AsynchronousOperation类的修订版,即:

  1. You must not call super.start(). As the start documentation says (emphasis added):

    你不能调用super.start()。正如开始文档所述(强调添加):

    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.

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

  2. Add @objc required in Swift 4.

    在Swift 4中添加@objc。

  3. Renamed execute to use main, which is the convention for Operation subclasses.

    将execute重命名为main,这是Operation子类的约定。

  4. It is inappropriate to declare isReady as a final property. Any subclass should have the right to further refine its isReady logic (though we admittedly rarely do so).

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

  5. Use #keyPath to make code a little more safe/robust.

    使用#keyPath使代码更安全/健壮。

  6. You don't need to do manual KVN when using dynamic property. The manual calling of willChangeValue and didChangeValue is not needed in this example.

    使用动态属性时,不需要手动KVN。在此示例中不需要手动调用willChangeValue和didChangeValue。

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

    更改完成,以便只有isExecuting才会移动到.finished状态。

Thus:

从而:

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.sync(flags: .barrier) { _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 }

    // 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 {
            finish()
            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 isExecuting { state = .finished }
    }
}

#2


1  

About your first question: stateQueue lock your operation when writing a new value to you operation state by:

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

    return stateQueue.sync(execute: {
            rawState
    })

And

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

as your operation is asynchronous so before read or write one state another state can be called. Like you want to write isExecution but in the mean time isFinished already called. So to avoid this scenario stateQueue lock the operation state to be read and write until it finished its previous call. Its work like Atomic. Rather use dispatch queue you can use an extension to NSLock to simplify executing critical code from Advanced NSOperations sample code in WWDC 2015 https://developer.apple.com/videos/play/wwdc2015/226/ from https://developer.apple.com/sample-code/wwdc/2015/downloads/Advanced-NSOperations.zip and you can implement like following:

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

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")
    }
}

About your second question: Its a KVO notification for the read only property isReady, isExecuting, isFinished to manage the operation state. You can read this: http://nshipster.com/key-value-observing post till the end for better understanding about KVO.

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

#1


18  

You said:

你说:

  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.
  2. stateQueue属性的目的是什么?我看到它被状态计算属性的get和set使用,但我找不到任何解释sync的文档:flags:execute和sync:执行他们使用的方法。

This code "synchronizes" access to a property to make it thread safe. Regarding why you need to do that, see the Operation documentation, which advises:

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

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.

...当您继承NSOperation时,必须确保从多个线程调用任何重写的方法都是安全的。如果在子类中实现自定义方法(例如自定义数据访问器),则还必须确保这些方法是线程安全的。因此,必须同步对操作中的任何数据变量的访问,以防止潜在的数据损坏。有关同步的更多信息,请参阅“线程编程指南”。

Regarding the exact use of this concurrent queue for synchronization, this is known as the "reader-writer" pattern. This basic concept of reader-writer pattern is that reads can happen concurrent with respect to each other (hence sync, with no barrier), but writes must never be performed concurrently with respect to any other access of that property (hence async with barrier). This is all described in WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC. Note, while that video outlines the basic concept, it uses the older dispatch_sync and dispatch_barrier_async syntax, rather than the Swift 3 and later syntax of just async and sync(flags: .barrier) syntax used here.

关于这个并发队列用于同步的确切用法,这被称为“读写器”模式。读写器模式的这个基本概念是读取可以相互发生并发(因此同步,没有障碍),但写入必须永远不会同时执行该属性的任何其他访问(因此与屏障异步) 。这在WWDC 2012视频异步设计模式与块,GCD和XPC中都有描述。请注意,虽然该视频概述了基本概念,但它使用较旧的dispatch_sync和dispatch_barrier_async语法,而不是此处使用的async和sync(flags:.barrier)语法的Swift 3及更高版本语法。

You also asked:

你还问:

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

These are just methods that ensure that changes to the state property trigger KVN for properties isReady, isExecuting and isFinished. The KVN 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.

这些只是确保对属性的更改触发属性isReady,isExecuting和isFinished的KVN的方法。这三个密钥的KVN对于异步操作的正确运行至关重要。无论如何,这个语法在Key-Value Observing Programming Guide:Registering Dependent Keys中有概述。

The keyPathsForValuesAffectingValue method you found is related. You can either register dependent keys using that method, or have the individual methods as shown in your original code snippet.

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


BTW, here is a revised version of the AsynchronousOperation class you provided, namely:

顺便说一句,这是您提供的AsynchronousOperation类的修订版,即:

  1. You must not call super.start(). As the start documentation says (emphasis added):

    你不能调用super.start()。正如开始文档所述(强调添加):

    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.

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

  2. Add @objc required in Swift 4.

    在Swift 4中添加@objc。

  3. Renamed execute to use main, which is the convention for Operation subclasses.

    将execute重命名为main,这是Operation子类的约定。

  4. It is inappropriate to declare isReady as a final property. Any subclass should have the right to further refine its isReady logic (though we admittedly rarely do so).

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

  5. Use #keyPath to make code a little more safe/robust.

    使用#keyPath使代码更安全/健壮。

  6. You don't need to do manual KVN when using dynamic property. The manual calling of willChangeValue and didChangeValue is not needed in this example.

    使用动态属性时,不需要手动KVN。在此示例中不需要手动调用willChangeValue和didChangeValue。

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

    更改完成,以便只有isExecuting才会移动到.finished状态。

Thus:

从而:

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.sync(flags: .barrier) { _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 }

    // 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 {
            finish()
            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 isExecuting { state = .finished }
    }
}

#2


1  

About your first question: stateQueue lock your operation when writing a new value to you operation state by:

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

    return stateQueue.sync(execute: {
            rawState
    })

And

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

as your operation is asynchronous so before read or write one state another state can be called. Like you want to write isExecution but in the mean time isFinished already called. So to avoid this scenario stateQueue lock the operation state to be read and write until it finished its previous call. Its work like Atomic. Rather use dispatch queue you can use an extension to NSLock to simplify executing critical code from Advanced NSOperations sample code in WWDC 2015 https://developer.apple.com/videos/play/wwdc2015/226/ from https://developer.apple.com/sample-code/wwdc/2015/downloads/Advanced-NSOperations.zip and you can implement like following:

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

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")
    }
}

About your second question: Its a KVO notification for the read only property isReady, isExecuting, isFinished to manage the operation state. You can read this: http://nshipster.com/key-value-observing post till the end for better understanding about KVO.

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