试图理解异步操作子类

Nic*_*hrn 20 nsoperation swift

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

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
}
Run Code Online (Sandbox Code Playgroud)

这个Operation子类有一些实现细节,我想帮助理解.

  1. stateQueue物业的目的是什么?我看到它正在使用的getset在的state计算性能,但我找不到解释的任何文件sync:flags:executesync:execute他们所使用的方法.

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

Rob*_*Rob 39

你说:

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

此代码"同步"对属性的访问以使其线程安全.至于为什么你需要做的是,看到Operation文件,其中建议:

多核注意事项

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

关于这个并发队列用于同步的确切用法,这被称为"读写器"模式.读写器模式的这个基本概念是读取可以相互发生并发生(因此sync,没有障碍),但是对于该属性的任何其他访问(因此async具有障碍),写入决不能同时执行.这些都在WWDC 2012视频异步设计模式与块,GCD和XPC中描述.请注意,而视频概述基本的概念,它使用旧的OperationNSObject语法,而不是仅仅的斯威夫特3及更高版本的语法["state"]NSObject这里使用的语法.

你还问:

  1. class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String>返回部分中三个类方法的目的是什么state?我没有看到它们被用在任何地方.我找到了isReady,isExecuting但是,这似乎没有帮助我理解为什么声明这些方法.

这些只是确保对isFinished属性的更改触发属性的KVN的方法keyPathsForValuesAffectingValue,AsynchronousOperation以及super.start().这三个密钥的KVN对于异步操作的正确运行至关重要.无论如何,在键值观察编程指南:注册从属密钥中概述了这种语法.

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


顺便说一句,这是super您提供的课程的修订版,即:

  1. 你不能打电话@objc.正如execute文档所述(强调添加):

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

  2. Operation在Swift 4中添加所需.

  3. 重命名isReady为use final,这是isReady子类的约定.

  4. 宣布#keyPathdynamic财产是不恰当的.任何子类都应该有权进一步完善其willChangeValue逻辑(尽管我们很少这样做).

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

  6. 使用finish属性时,您不需要手动KVN .本例中手动调用.finishedstateQueue不需要.

  7. 改变,state以便它只移动到sync:flags:execute状态if sync:execute.

从而:

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 { $0[index] = newValue } }
    }

    func append(_ value: T) {
        writer { $0.append(value) }
    }

    func remove(at index: Int) {
        writer { $0.remove(at: index)}
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我不同意.[并发编程指南](https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html#//apple_ref/doc/uid/TP40008091-CH101-SW16)鼓励使用`start`和`main`进行异步操作.在讨论`main`和异步操作时,他们说,"虽然你可以在`start`方法中执行任务,但使用[`main`]实现任务可以使你的设置和任务代码更清晰地分离. ". (3认同)
  • @VinGazoil - 它代表“读写器”,一种使用并发队列的同步模式,允许并发读取,但对写入使用屏障以确保写入正确同步。 (2认同)

小智 7

使用Rob 的回答中更新的代码片段时,应该注意由此更改引起的错误的可能性:

  1. 更改完成,使其仅在 isExecuting 时才移动到 .finished 状态。

以上与 Apple文档背道而驰

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

在少数情况下,这会导致错误。例如,如果“maxConcurrentOperationCount = 1”的操作队列获得3个异步操作AB和C,那么如果在A期间所有操作都被取消,则C将不会被执行,队列将卡在操作B上。