🐈

【翻訳】Parallel Programming with Swift — Part 4/4

2023/07/22に公開

この記事は、 Swift による並列プログラミングシリーズのパート4です。パート1では、 Dispatch Queue とシステムが提供するキューについて調べました。パート2では、タスクを定義する別の方法と、 GCD が提供する強力な API に焦点を当てました。パート3では、 Operation と Operation Queue について説明しました。今回は、オペレーションを作成し、オペレーション・キューに追加してみましょう。

このシリーズをすべてご覧になりたい方は、こちらをクリックしてください。

Concurrency & GCD — Parallel Programming with Swift — Part 1/4 (https://medium.com/swift-india/parallel-programming-with-swift-part-1-4-df7caac564ae)

GCD — Parallel Programming with Swift — Part 2/4 (https://medium.com/swift-india/parallel-programming-with-swift-part-2-4-46a3c6262359)

ブロック・オペレーション:

1つまたは複数のブロックの同時実行を管理するオペレーション。 BlockOperation クラスは Operation クラスを継承しています。このオブジェクトを使用すると、複数のブロックを同時に実行することができます。複数のブロックを実行する場合、全てのブロックの実行が終了して初めて Operation 自体が終了したとみなされます。

ブロック・オペレーションに追加されたブロックは、デフォルトの優先順位で適切な作業キューにディスパッチされます。

let queue = OperationQueue()

for i in 1...3 {
    let operation = BlockOperation()
    operation.addExecutionBlock {
        if !operation.isCancelled {
                print("###### Operation \(i) in progress ######")
                let imageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
                let _ = try! Data(contentsOf: imageURL)
                
                OperationQueue.main.addOperation {
                    print("Image \(i) downloaded...")
                }
        }
    }
    operation.queuePriority = .high
    queue.addOperation(operation)
}

queue.maxConcurrentOperationCount = 2
queue.waitUntilAllOperationsAreFinished()
queue.cancelAllOperations()

addDependency メソッドを使用することで、指定した operation の完了時に operation を実行することができます。

let queue = OperationQueue()

for i in 1...3 {
    let dependentOperation = BlockOperation()
    dependentOperation.addExecutionBlock {
        if !dependentOperation.isCancelled {
            print("###### Operation \(i) in progress ######")
            let imageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imageURL)
            
            print("Image \(i) downloaded...")
        }
    }
    dependentOperation.queuePriority = .high
    
    let operation = BlockOperation {
        print("Execute Operation \(i), Once dependent work is done")
    }
    operation.addDependency(dependentOperation)
    queue.addOperation(operation)
    queue.addOperation(dependentOperation)
}

queue.maxConcurrentOperationCount = 2
queue.waitUntilAllOperationsAreFinished()
queue.cancelAllOperations()

NSInvocationOperation:

Objective C では NSInvocationOperation を作成できますが、 Swift では利用できません。

非同期オペレーション:

Operation クラスをサブクラス化することで、非同期オペレーションを作成することができます。

//  Created by Vasily Ulianov on 09.02.17, updated in 2019.
//  License: MIT
import Foundation

/// Subclass of `Operation` that adds support of asynchronous operations.
/// 1. Call `super.main()` when override `main` method.
/// 2. When operation is finished or cancelled set `state = .finished` or `finish()`
open class AsynchronousOperation: Operation {
    public override var isAsynchronous: Bool {
        return true
    }
    
    public override var isExecuting: Bool {
        return state == .executing
    }
    
    public override var isFinished: Bool {
        return state == .finished
    }
    
    public override func start() {
        if self.isCancelled {
            state = .finished
        } else {
            state = .ready
            main()
        }
    }
    
    open override func main() {
        if self.isCancelled {
            state = .finished
        } else {
            state = .executing
        }
    }
    
    public func finish() {
        state = .finished
    }
    
    // MARK: - State management
    
    public enum State: String {
        case ready = "Ready"
        case executing = "Executing"
        case finished = "Finished"
        fileprivate var keyPath: String { return "is" + self.rawValue }
    }
    
    /// Thread-safe computed state value
    public var state: State {
        get {
            stateQueue.sync {
                return stateStore
            }
        }
        set {
            let oldValue = state
            willChangeValue(forKey: state.keyPath)
            willChangeValue(forKey: newValue.keyPath)
            stateQueue.sync(flags: .barrier) {
                stateStore = newValue
            }
            didChangeValue(forKey: state.keyPath)
            didChangeValue(forKey: oldValue.keyPath)
        }
    }
    
    private let stateQueue = DispatchQueue(label: "AsynchronousOperation State Queue", attributes: .concurrent)
    
    /// Non thread-safe state storage, use only with locks
    private var stateStore: State = .ready
}

operation間のデータの受け渡し

operation 間でデータを受け渡すには、さまざまなアプローチがあります。主に採用されているのはアダプター・パターンです。ここでは、新しい BlockOperation を作成してデータを渡します。この記事では、全てのアプローチを分かりやすく紹介します。

https://www.marcosantadev.com/4-ways-pass-data-operations-swift/

Grand Central Dispatch より Operation を使用する利点

依存関係

Operation は、特定の順序でタスクを実行することを可能にする、 operation 間の依存関係を追加する API を提供します。Operationは、すべての依存関係の実行が終了した時点で準備完了となります。

Observable

Operation と OperationQueue には、 KVO を使用して観察できる多くのプロパティがあります。

operation の状態

オペレーションやオペレーション・キューの状態を監視できます。

制御

operation の一時停止、キャンセル、再開ができます。

最大同時動作できるキューの数

同時に実行できるオペレーションキューの最大数を指定できます。maxConcurrentOperationCount に 1 を指定することで、シリアルオペレーションキューを作成できます。

【翻訳元の記事】

Parallel Programming with Swift — Part 4/4
https://medium.com/swift-india/parallel-programming-with-swift-part-4-4-f3c8459cb017

Discussion