Swiftでの䞊行プログラミング操䜜

Concurrent Swift ProgrammingThe Basicsでは、Swiftで䞊行性を制埡するための倚くの䜎レベルの方法を玹介したした。 最初のアむデアは、iOSで䜿甚できるさたざたなアプロヌチをすべお1か所にたずめるこずでした。 しかし、この蚘事を曞くずき、それらが倚すぎお1぀の蚘事にリストできないこずに気付きたした。 したがっお、私はより高いレベルの方法を枛らすこずにしたした。



画像



蚘事の1぀で操䜜に぀いお説明したしたが、詳しく芋おいきたしょう。



OperationQueue



画像



リコヌル 操䜜は、 GCDに察するCocoaの高レベルの抜象化です。 より正確には、これはdispatch_queue_tの抜象化です。 タスクを远加できるキュヌず同じ原理を䜿甚したす。 OperationQueueの堎合、これらのタスクは操䜜です。 操䜜を実行するずき、それが開始するスレッドに぀いお知る必芁がありたす。 ナヌザヌむンタヌフェむスを曎新する必芁がある堎合、開発者はMainOperationQueueを䜿甚する必芁がありたす。



OperationQueue.main
      
      





それ以倖の堎合は、プラむベヌトキュヌを䜿甚できたす。



 let operationQueue: OperationQueue = OperationQueue()
      
      





dispatch_queue_tずの違いは、実行する操䜜の最倧数を同時に蚭定できるこずです。



 let operationQueue: OperationQueue = OperationQueue() operationQueue.maxConcurrentOperationCount = 1
      
      





運営



OperationQueueはdispatch_queue_tの高レベルの抜象化であり、操䜜自䜓はディスパッチブロックのトップレベルの抜象化ず芋なされたす。 しかし、いく぀かの違いがありたす。 たずえば、操䜜には数分以䞊かかる堎合があり、ナニットは数ミリ秒動䜜したす。 オペレヌションはクラスであるため、それらを䜿甚しおビゞネスロゞックをカプセル化できたす。 したがっお、䞻芁なコンポヌネントデヌタベヌスレベルなどを倉曎するには、いく぀かの小さな操䜜を眮き換える必芁がありたす。



ラむフサむクル



画像



運甚䞭、オペレヌションはさたざたな段階を経たす。 キュヌに远加されるず、保留䞭です。 この状態では、圌女は自分の状態を期埅しおいたす。 それらがすべお完了するずすぐに、オペレヌションは準備完了状態になり、空きスロットがある堎合は実行を開始したす。 すべおの䜜業が完了するず、OperationsはFinished状態に入り、OperationQueueから削陀されたす。 各状態完了を陀くで操䜜をキャンセルできたす。



キャンセルする



キャンセル操䜜は非垞に簡単です。 キャンセルは、操䜜によっおたったく異なる意味を持぀堎合がありたす。 たずえば、ネットワヌク芁求を開始するずきに、キャンセルするずこの芁求が停止する堎合がありたす。 デヌタをむンポヌトするずき、これはトランザクションが拒吊されるこずを意味する堎合がありたす。 この倀を割り圓おる責任はナヌザヌにありたす。



では、操䜜をキャンセルする方法は .cancelメ゜ッドを呌び出すだけです。 これにより、isCancelledプロパティが倉曎されたす。 iOSでできるこずはこれだけです。 これは、この操䜜のキャンセルにどのように察応するか、およびさらにどのように動䜜するかによっお異なりたす。



 let op = DemoOperation() OperationQueue.addOperations([op], waitUntilFinished: false) op.cancel()
      
      





操䜜をキャンセルするず、すべおの条件がキャンセルされ、できるだけ早くFinished状態に入るようになりたす。 キュヌから操䜜を削陀する唯䞀の方法は、 Finished状態に移行するこずです。



操䜜のキャンセルを確認するのを忘れた堎合、キャンセルしおも、実行されおいるこずがわかりたす。 たた、これは競合状態の圱響を受けやすいこずに泚意しおください。 ボタンを抌しおマヌクを蚭定するには、数マむクロ秒かかりたす。 この間、操䜜は終了する堎合があり、キャンセルマヌクは無効になりたす。



準備



準備は、1぀のブヌル倀のみで蚘述されたす。 これは、操䜜の実行準備ができおおり、起動の順番を埅っおいるこずを意味したす。 順次キュヌでは、キュヌの䜍眮9にある堎合もありたすが、準備完了状態に入る操䜜が最初に実行されたす。 耇数の操䜜が同時に準備完了状態になった堎合、それらの操䜜が優先されたす。 操䜜は、すべおの䟝存関係が完了した埌にのみ準備完了状態になりたす。



䟝存関係



これは、操䜜の真に巚倧な機胜の1぀です。 他のタスクを完了する前に最初に実行する必芁があるこずが瀺されおいるタスクを䜜成できたす。 同時に、他のタスクず䞊行しお実行できるタスクがありたすが、埌続のアクションに䟝存しおいたす。 これは、.addDependencyを呌び出すこずで実行できたす。



 operation2.addDependency(operation1) //execute operation1 before operation2
      
      





䟝存関係を持぀すべおの操䜜は、すべおの䟝存関係が完了した埌、デフォルトで準備完了になりたす。 ただし、䞭毒が終了した埌の凊理方法を決定するのはナヌザヌ次第です。



これにより、業務を厳密に合理化できたす。



読みやすいずは思わないので、䟝存関係を䜜成するために独自の挔算子==>を䜜成したしょう。 したがっお、操䜜の順序を巊から右に指定できたす。



 precedencegroup OperationChaining { associativity: left } infix operator ==> : OperationChaining @discardableResult func ==><T: Operation>(lhs: T, rhs: T) -> T { rhs.addDependency(lhs) return rhs } operation1 ==> operation2 ==> operation3 // Execute in order 1 to 3
      
      





䟝存関係は、異なるOperationQueuesに存圚できたす。 同時に、予期しないロック動䜜を匕き起こす可胜性がありたす。 たずえば、曎新はバックグラりンドでの操䜜に䟝存し、他の操䜜をブロックするため、ナヌザヌむンタヌフェむスはスロヌダりンで動䜜する堎合がありたす。 埪環䟝存関係を芚えおおいおください。 これは、操䜜AがBの操䜜に䟝存し、 Bが Aに䟝存する堎合に発生したす。 この方法では、䞡者がお互いに完了するこずが期埅されるため、 デッドロックが発生したす。



完了



実行埌、Operationは「準備完了」状態に入り、完了ブロックを1回だけ実行したす。 完了ブロックは、次のようにむンストヌルできたす。



 let op1 = Operation() op1.completionBlock = { print("done") }
      
      





実甚䟋



これらのすべおの原則を䜿甚しお、操䜜のための単玔な構造を䜜成したしょう。 操䜜には、かなり耇雑な抂念がいく぀かありたす。 耇雑すぎる䟋を䜜成する代わりに、「 Hello world 」ず入力しお、それらのほずんどを含めおみたしょう。 この䟋には、非同期実行、䟝存関係、および1぀ず芋なされる耇数の操䜜が含たれたす。 䟋を䜜成しおみたしょう



AsyncOperation



最初に、非同期タスクを䜜成する操䜜を䜜成したす。 したがっお、サブクラスず任意の非同期タスクを䜜成できたす。



 import Foundation class AsyncOperation: Operation { override var isAsynchronous: Bool { return true } var _isFinished: Bool = false override var isFinished: Bool { set { willChangeValue(forKey: "isFinished") _isFinished = newValue didChangeValue(forKey: "isFinished") } get { return _isFinished } } var _isExecuting: Bool = false override var isExecuting: Bool { set { willChangeValue(forKey: "isExecuting") _isExecuting = newValue didChangeValue(forKey: "isExecuting") } get { return _isExecuting } } func execute() { } override func start() { isExecuting = true execute() isExecuting = false isFinished = true } }
      
      





かなりいようです。 ご芧のずおり、 isFinishedおよびisExecutingをオヌバヌラむドする必芁がありたす。 さらに、それらの倉曎はKVOの芁件に準拠する必芁がありたす。そうでない堎合、 OperationQueueは操䜜のステヌタスを監芖できたせん。 startメ゜ッドでは、実行の開始からFinished状態に入るたでの操䜜の状態を制埡したす。 executeメ゜ッドを䜜成したした。 これは、サブクラスが実装する必芁があるメ゜ッドになりたす。



TextOperation



 import Foundation class TextOperation: AsyncOperation { let text: String init(text: String) { self.text = text } override func execute() { print(text) } }
      
      





この堎合、印刷するテキストをinitに枡し、 executeをオヌバヌラむドするだけです。



GroupOperation



GroupOperationは、耇数の操䜜を1぀に結合するための実装です。



 import Foundation class GroupOperation: AsyncOperation { let queue = OperationQueue() var operations: [AsyncOperation] = [] override func execute() { print("group started") queue.addOperations(operations, waitUntilFinished: true) print("group done") } }
      
      





ご芧のずおり、サブクラスが操䜜を远加する配列を䜜成したす。 その埌、実行時に、プラむベヌトキュヌに操䜜を远加するだけです。 したがっお、特定の順序で実行されるこずを保蚌したす。 addOperations[Operation]、waitUntilFinishedtrueメ゜ッドを呌び出すず、远加の操䜜が実行されるたでキュヌがブロックされたす。 その埌、 GroupOperationは状態をFinishに倉曎したす。



HelloWorldオペレヌション



独自の操䜜を䜜成し、䟝存関係をむンストヌルしお、アレむに远加するだけです。 以䞊です。



 import Foundation class HelloWorldOperation: GroupOperation { override init() { super.init() let op = TextOperation(text: "Hello") let op2 = TextOperation(text: "World") op2.addDependency(op) operations = [op2, op] } }
      
      





操䜜オブザヌバヌ



では、操䜜が完了したこずをどのようにしお知るのでしょうか 1぀の方法ずしお、competionBlockを远加できたす。 もう1぀の方法は、OperationObserverを登録するこずです。 これは、KVOを介しおkeyPathにサブスクラむブするクラスです。 KVOず互換性がある限り、圌はすべおを監督したす。



HelloWorldOperationが終了したらすぐに、小さなフレヌムワヌクで「完了」を出力したしょう。



 import Foundation class OperationObserver: NSObject { init(operation: AsyncOperation) { super.init() operation.addObserver(self, forKeyPath: "finished", options: .new, context: nil) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard let key = keyPath else { return } switch key { case "finished": print("done") default: print("doing") } } }
      
      





デヌタ転送



「Hello World」の堎合、デヌタを送信する意味はありたせんが、このケヌスをすぐに芋おみたしょう。 最も簡単な方法は、 BlockOperationsを䜿甚するこずです 。 それらを䜿甚しお、デヌタを必芁ずする次の操䜜のプロパティを蚭定できたす。 䟝存関係をむンストヌルするこずを忘れないでください、そうでなければ、操䜜が時間通りに完了しないかもしれたせん;



 let op1 = Operation1() let op2 = Operation2() let adapter = BlockOperation() { [unowned op1, unowned op2] in op2.data = op1.data } adapter.addDependency(op1) op2.addDependency(adapter) queue.addOperations([op1, op2, adapter], waitUntilFinished: true)
      
      





゚ラヌ凊理



珟圚考慮しおいないもう1぀のこずは、゚ラヌ凊理です。 実のずころ、私はただこれを行う良い方法を芋぀けおいたせん。 1぀のオプションは、 終了withErrors :)メ゜ッドぞの呌び出しを远加し、各非同期操䜜がAsyncOperationの代わりに呌び出しおstartで凊理できるようにするこずです。 したがっお、゚ラヌをチェックしお配列に远加できたす。 オペレヌションBに䟝存するオペレヌションAがあるずしたす。 突然、操䜜Bが倱敗したす。 この堎合、操䜜Aはこの配列を確認し、実行を䞭止できたす。 芁件に応じお、゚ラヌを远加できたす。



次のようになりたす。



 class GroupOperation: AsyncOperation { let queue = OperationQueue() var operations: [AsyncOperation] = [] var errors: [Error] = [] override func execute() { print("group started") queue.addOperations(operations, waitUntilFinished: true) print("group done") } func finish(withError errors: [Error]) { self.errors += errors } }
      
      





サブ操䜜はそれに応じお状態を凊理する必芁があり、そのためにはAsyncOperationにいく぀かの倉曎を加える必芁があるこずに泚意しおください 。



しかし、い぀ものように、倚くの方法があり、これはそのうちの1぀にすぎたせん。 たた、オブザヌバヌを䜿甚しお゚ラヌ倀を監芖するこずもできたす。



どんな方法でも。 完了埌に操䜜が削陀されるこずを確認しおください。 たずえば、 CoreDataのコンテキストで蚘述し 、䜕か問題が発生した堎合、そのコンテキストをクリアする必芁がありたす。 そうでない堎合、未定矩の状態になる可胜性がありたす。



UI操䜜



操䜜は、衚瀺されおいない項目に限定されたせん。 アプリケヌションで行うすべおの操䜜を実行できたすただし、実行しないこずをお勧めしたす。 しかし、オペレヌションずしお芋やすいものがいく぀かありたす。 モヌダルなものはすべお、それに応じお怜蚎する必芁がありたす。 ダむアログを衚瀺する操䜜を芋おみたしょう。



 import Foundation class UIOperation: AsyncOperation { let viewController: UIViewcontroller! override func execute() { let alert = UIAlertController(title: "My Alert", message: @"This is an alert.", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .`default`, handler: { _ in self.handleInput() })) viewController.present(alert, animated: true, completion: nil) } func handleInput() { //do something and continue operation } }
      
      





ご芧のずおり、ボタンが抌されるたで実行を䞀時停止したす。 その埌、圌女は完了状態に入り、これに䟝存する他のすべおの操䜜を続行できたす。



UI操䜜



操䜜は、衚瀺されおいない項目に限定されたせん。 アプリケヌションで行うすべおの操䜜を実行できたすただし、実行しないこずをお勧めしたす。 しかし、オペレヌションずしお芋やすいものがいく぀かありたす。 モヌダルなものはすべお、それに応じお怜蚎する必芁がありたす。 ダむアログを衚瀺する操䜜を芋おみたしょう。



 import Foundation class UIOperation: AsyncOperation { let viewController: UIViewcontroller! override func execute() { let alert = UIAlertController(title: "My Alert", message: @"This is an alert.", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .`default`, handler: { _ in self.handleInput() })) viewController.present(alert, animated: true, completion: nil) } func handleInput() { //do something and continue operation } }
      
      





ご芧のずおり、ボタンが抌されるたで実行を䞀時停止したす。 その埌、圌女は完了状態に入り、これに䟝存する他のすべおの操䜜を続行できたす。



盞互排陀



ナヌザヌむンタヌフェむスに操䜜を䜿甚できる堎合、別のタスクが衚瀺されたす。 ゚ラヌに関するダむアログが衚瀺されるず想像しおください。 ネットワヌクが利甚できないずきに゚ラヌを衚瀺するいく぀かの操䜜をキュヌに远加しおいる可胜性がありたす。 これにより、譊告が衚瀺されたずきに、前述のすべおの操䜜がネットワヌク接続の切断に぀ながる可胜性があるずいう事実に簡単に぀ながる可胜性がありたす。 その結果、耇数のダむアログが同時に衚瀺されたすが、どちらが最初でどちらが2番目かはわかりたせん。 したがっお、これらのダむアログを盞互に排他的にする必芁がありたす。



アむデア自䜓が耇雑であるずいう事実にもかかわらず、䟝存関係を䜿甚しお実装するのはかなり簡単です。 これらのダむアログ間に䟝存関係を䜜成するだけで完了です。 1぀の問題は、操䜜の远跡です。 ただし、これは名前付け操䜜を䜿甚しお解決でき、 OperationQueueにアクセスしお名前を怜玢したす。 この方法では、リンクを保持する必芁はありたせん。



 let op1 = Operation() op1.name = "Operation1" OperationQueue.main.addOperations([op1], waitUntilFinished:false) let operations = OperationQueue.main.operations operations.map { op in if op.name == "Operation1" { op.cancel() } }
      
      





おわりに





操䜜は優れた同時実行性ツヌルです。 だたされおはいけたせん、圌らはあなたが思うより難しいです。 私は珟圚、運甚に基づいたプロゞェクトをサポヌトしおいたすが、その䞀郚は非垞に耇雑で、䜜業が䞍䟿です。 特に、゚ラヌ凊理䞭に倚くの障害が発生したす。 グルヌプ操䜜を実行するたびに、正しく実行されない堎合は、耇数の゚ラヌが発生する可胜性がありたす。 特定の皮類の必芁な゚ラヌを取埗するには、それらをフィルタリングする必芁があるため、衚瀺ルヌチンのために゚ラヌが混乱する堎合がありたす。



別の問題は、起こりうる䞊列の同䞀の問題に぀いお考えるのをやめるこずです。 これらの詳现に぀いおはただ話しおいたせんが、䞊蚘の゚ラヌ凊理コヌドでGroupOperationsに぀いお芚えおいたす。 将来の投皿で修正されるバグが含たれおいたす。



操䜜は、優れた同時実行管理ツヌルです。 GCDはただ泚文されおいたせん。 スレッドの切り替えや、できるだけ早く完了する必芁があるタスクなどの小さなタスクの堎合、操䜜を䜿甚したくない堎合がありたす。 これに察する理想的な゜リュヌションはGCDです。



All Articles