paint-brush
OperationQueue + 非同期コード: 知っておくべきことすべて@bugorbn
528 測定値
528 測定値

OperationQueue + 非同期コード: 知っておくべきことすべて

Boris Bugor5m2024/03/17
Read on Terminal Reader

長すぎる; 読むには

Swift では、同期コードに操作キューを使用するのはまったくの地獄のように思えるかもしれません。これは、コードのコンパイルが完了すると、内部的にはコードが完了したとみなされるためです。非同期コードが実行されるまでに、「操作」自体はすでに完了しています。問題の解決方法を理解するには、操作のライフサイクルがどのように機能するかを理解する必要があります。
featured image - OperationQueue + 非同期コード: 知っておくべきことすべて
Boris Bugor HackerNoon profile picture

Swift では、非同期コードにOperationQueue使用するのはまったくの地獄のように思えるかもしれません。同期コードのコンパイルが完了すると、内部的にはOperationsが完了したとみなされるからです。


言い換えれば、以下で説明する例をコンパイルすると、非同期コードが実行されるまでにOperation自体がすでに完了しているため、壊れた実行順序が出力されます。


 let operationQueue = OperationQueue() operationQueue.maxConcurrentOperationCount = 1 operationQueue.addOperation { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { print("First async operation complete") } print("First sync operation complete") } operationQueue.addOperation { DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { print("Second async operation complete") } print("Second sync operation complete") }


このコードは次のように出力します:


 First sync operation complete Second sync operation complete First async operation complete Second async operation complete


ただし、これらの制限を回避する方法があります。問題の解決方法を理解するには、 Operation内部でどのように機能するかを理解する必要があります。


Operation自体には、オペレーションのライフ サイクルを追跡できる 4 つのフラグがあります。


  • isReady — 現時点でOperationを実行できるかどうかを示します。


  • isExecuting - Operation現在進行中かどうかを示します。


  • isFinished - Operation現在完了しているかどうかを示します。


  • isCancelledOperationキャンセルされたかどうかを示します。


理論的には、 Operation自体が非同期で実行される前に、 OperationisFinished状態になるため、 Operationのライフサイクルを操作できる手法を開発する必要があります。


この可能性は、 Operationサブクラス化し、 start / cancelメソッドとオペレーションのライフ サイクルを構築するすべてのフラグを再定義することによって解決できます。


コードは次のとおりです。


 public class AsyncOperation: Operation { // MARK: Open override open var isAsynchronous: Bool { true } override open var isReady: Bool { super.isReady && self.state == .ready } override open var isExecuting: Bool { self.state == .executing } override open var isFinished: Bool { self.state == .finished } override open func start() { if isCancelled { state = .finished return } main() state = .executing } override open func cancel() { super.cancel() state = .finished } // MARK: Public public enum State: String { case ready case executing case finished // MARK: Fileprivate fileprivate var keyPath: String { "is" + rawValue.capitalized } } public var state = State.ready { willSet { willChangeValue(forKey: newValue.keyPath) willChangeValue(forKey: state.keyPath) } didSet { didChangeValue(forKey: oldValue.keyPath) didChangeValue(forKey: state.keyPath) } } }


Operationから受け取ったサブクラスは基本的なもので、手動で強制的に完了することができます。


完了ブロックを操作するには、別のサブクラスを作成する必要があります。ただし、これはOperationのサブクラスではなく、 AsyncOperationのサブクラスになります。


 public typealias VoidClosure = () -> Void public typealias Closure<T> = (T) -> Void public class CompletionOperation: AsyncOperation { // MARK: Lifecycle public init(completeBlock: Closure<VoidClosure?>?) { self.completeBlock = completeBlock } // MARK: Public override public func main() { DispatchQueue.main.async { [weak self] in self?.completeBlock? { DispatchQueue.main.async { self?.state = .finished } } } } // MARK: Private private let completeBlock: Closure<VoidClosure?>? }


このサブクラスを使用すると、クロージャをOperationに渡すことができ、その後Operationが完了します。


このタイプの操作を実際に試してみましょう。


 let operationQueue = OperationQueue() operationQueue.maxConcurrentOperationCount = 1 operationQueue.addOperation( CompletionOperation { completion in DispatchQueue.main.asyncAfter(deadline: .now() + 1) { print("First async operation complete") completion?() } print("First sync operation complete") } ) operationQueue.addOperation( CompletionOperation { completion in DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { print("Second async operation complete") completion?() } print("Second sync operation complete") } )


その結果、 Operationsの同期実行を実現できました。


 First sync operation complete First async operation complete Second sync operation complete Second async operation complete


遠慮せずに私に連絡してくださいツイッターご質問がございましたら。また、いつでもできますコーヒーを買ってください


ここでも公開されています