web-dev-qa-db-ja.com

非同期操作サブクラスを理解しようとする

ネストされた呼び出しを排除するために、ネットワークコード全体にクロージャベースのコールバックを散らばらせるのではなく、サイドプロジェクトで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サブクラスの実装の詳細がいくつかありますので、理解を深めてください。

  1. stateQueueプロパティの目的は何ですか? get計算プロパティのsetstateで使用されているのはわかりますが、それらが使用するsync:flags:executeメソッドとsync:executeメソッドを説明するドキュメントは見つかりません。

  2. ["state"]を返すNSObjectセクションの3つのクラスメソッドの目的は何ですか?どこでも使われていないようです。 NSObjectclass func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String>で見つかりましたが、これらのメソッドが宣言されている理由を理解するのに役立ちません。

22
Nick Kohrn

Rob's answer から更新されたコードスニペットを使用する場合、この変更によって引き起こされるバグの可能性に注意する必要があります。

  1. IsExecutingの場合にのみ.finished状態に移行するようにfinishを変更します。

上記はApple docs に反します:

操作がキャンセルされたときに単に終了するだけでなく、キャンセルされた操作を適切な最終状態に移動することも重要です。特に、完了したプロパティと実行中のプロパティの値を自分で管理している場合(おそらく、同時操作を実装しているため)、それに応じてそれらのプロパティを更新する必要があります。具体的には、finishedによって返される値をYESに、そして実行によって返される値をNOに変更する必要があります。操作が実行を開始する前にキャンセルされた場合でも、これらの変更を行う必要があります。

これにより、いくつかのケースでバグが発生します。たとえば、「maxConcurrentOperationCount = 1」の操作キューが3つの非同期操作A BおよびCを取得した場合、A中にすべての操作がキャンセルされると、Cは実行されず、キューは操作Bでスタックします。

3
Roman Kozak

最初の質問について:次の方法で、stateQueueは、操作の状態に新しい値を書き込むときに操作をロックします。

    return stateQueue.sync(execute: {
            rawState
    })

そして

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

あなたの操作は非同期なので、ある状態を読み書きする前に別の状態を呼び出すことができます。 isExecutionを記述したいが、その間にisFinishedがすでに呼び出されているように。したがって、このシナリオを回避するには、前の呼び出しが完了するまで、stateQueueが操作状態を読み取りおよび書き込みにロックします。原子のような作品。代わりにディスパッチキューを使用すると、NSLockの拡張機能を使用して、WWDC 2015のAdvanced NSOperationsサンプルコードから重要なコードを簡単に実行できます https://developer.Apple.com/videos/play/wwdc2015/226/ from 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")
    }
}

2番目の質問について:操作状態を管理するための読み取り専用プロパティisReady、isExecuting、isFinishedのKVO通知。これを読むことができます: http://nshipster.com/key-value-observing KVOについての理解を深めるために最後まで投稿してください。

1
Evana