Swift 3および4の同時実行性







ネットワークからのデータのダウンロードや画像処理などの時間のかかるコードを実行してiOS



アプリケーションのUI



応答性を実現する場合は、マルチスレッドに関連する高度なパターン( oncurrency



)を使用する必要があります。そうでない場合は、ユーザーインターフェイス( U



I)大幅に減速し始め、完全な「凍結」に至ることさえあります。 main thread



からリソースを消費するタスクを削除する必要があります。 main thread



は、ユーザーインターフェイス( UI



)を表示するコードを実行します。



現在のバージョンのSwift 3



および最も近いSwift 4



(2017年秋)では、これは、 Swift



組み込み言語コンストラクトにまだ関連付けられていない2つの方法で実行できます。その実装は、 Swift 5



(2018年末)でのみ開始されます。



それらの1つはGCD (Grand Central Dispatch)



おり、 前の記事はそれに専念しています。 この記事では、Operation操作やOperation



操作キューなどの抽象的な概念を使用して、 iOS



アプリケーションでUI



応答性を実現する方法を示します。 また、これら2つのアプローチの違いと、どの状況でどちらを使用するのが適切かを示します。



この記事のコードはGithubで見ることができます。



Operation



とは Operation



適切な定義は、 NSHipsterに記載されています。

Operation



は完了したタスクであり、操作の状態、その優先度、他のOperations



からの依存関係をシミュレートし、この操作を管理するためのスレッドセーフ構造を提供する抽象クラスです。


基本的な概念。 Operation





最も単純なOperation



は、通常のクロージャで表すことができ、これもDispatchQueue



で実行できます。 ただし、この形式の操作は、 addOperation



メソッドを使用してOperationQueue



追加する場合にのみ適用できます。









完全な操作操作は、 BlockOperation



イニシャBlockOperation



を使用して構築できます。 独自のstart ()



メソッドを使用してstart ()



ます。









同期関数の非同期バージョンのような再利用可能なものを取得したい場合、 Operation



クラスのカスタムsubclass



を作成し、そのインスタンスを取得する必要があります。









対応するフィルターを使用してぼやけた画像を取得するFilterOperation



操作は、 Operation



クラスのユーザーsubclass



として定義されます。 ユーザークラスには、入力プロパティと出力プロパティの両方、および他のヘルパー関数を含めることができます。 操作の機能部分に対応するために、 main ()



メソッドを再定義( override



)しました。



Operation



クラスを使用すると、 OperationQueue



操作キューで将来実行できるタスクを作成できますが、現時点では、他のOperations



するまで待機できます。



Operation



にはstate mashine



、これは操作Operation



「ライフサイクル」です。







操作操作の可能な状態は、 pending



(保留)、 ready



(実行準備完了)、 executing



(実行済み)、 finished



(完了済み)、 cancelled



(破棄済み)です。



Operation



を作成してOperation



に配置するとき、操作をpending



設定します。 しばらくすると、 ready



状態になり、いつでもexecuting



状態に移行してOperationQueue



送信して実行できます。 executing



状態は、ミリ秒から数分以上続くことがあります。 完了後、 Operation



はfinal状態のfinished



進みます。 この単純な「ライフ」サイクルの任意の時点で、Operation操作は破棄され、 cancelled



状態になります。



Operation



クラスAPI



は、 Operation



この「ライフサイクル」を反映しており、以下に示されています。









start()



メソッドを使用して、Operation操作を実行することができますが、ほとんどの場合、 OperationQueue



操作キューに操作を追加すると、このキューは自動的に操作を開始します。 start()



を使用してstart()



される別個のOperation操作は、 現在のスレッドで同期的に実行されることに注意してください。 現在のスレッドの外部で実行するには、 OperationQueue



またはDispatchQueue



を使用する必要があります。



アプリケーション内の任意の時点でのOperation操作の現在の状態は、ブールプロパティを使用して監視できます: isReady



isExecuting



isFinished



isCancelled



KVOkey-value observation



)メカニズムを使用しkey-value observation



操作自体はどのスレッドでも実行でき、最も必要な情報main thread



または操作自体が実行されるスレッド以外の他のスレッドで。



Operation操作の機能を追加する場合は、 subclass



Operation



作成する必要があります。 最も単純な場合、このsubclass



は、 Operation



クラスのmain()



メソッドをオーバーライドする必要があります。 Operation



クラス自体が操作の状態の変化を自動的に制御しますが、以下に示すより複雑なケースでは、これを手動で行う必要があります。



操作には、操作の完了後に実行されるcompletionBlock



と、 OperationQueue



での操作の優先度に影響を与える「サービス品質」 qualityOfService



を提供できます。



ご覧のとおり、 Operation



クラスにはcancel()



メソッドがありますが、このメソッドを使用するとisCancelled



プロパティがtrue



設定されるだけで、操作を「削除」するという意味はsubclass



Operation



作成時にのみ決定できます。 たとえば、ネットワークからデータをダウンロードする場合、 cancel()



をネットワーク相互作用から操作を切断するcancel()



定義できます。



基本的な概念。 OperationQueue





自分で操作を開始する代わりに、OperationQueueキューを使用して操作を管理します。 Operation Queue OperationQueue



は、追加の機能を備えたDispatchQueue



優先度の高い「ラッパー」と見なすことができます。実行された操作を破棄する機能、依存する操作を実行する機能などです。



OperationQueue



クラスAPI



見てみましょう。











ここでは、 OperationQueue ()



操作キューの最も単純な初期化子と、 OperationQueue ()



およびmain



キューの現在の操作キューを決定するcurrent



およびmain



2つのクラスプロパティを参照します。これは、 GCD



DispatchQueue.main



と同様にユーザーインターフェイス( UI



)を更新するために使用されます 非常に重要なプロパティmaxConcurrentOperationCount



は、このキューの同時操作の数を設定し、 1に設定すると、操作の順次( serial



)キューを確立します。





デフォルトでは、 maxConcurrentOperationCount



プロパティの値はDefault



に設定されます。これは、同時操作の最大可能数を意味します。







OperationQueue



操作(またはそのsubclass



いずれか)をOperation



subclass



、クロージャー、または操作の配列全体に直接追加して、 OperationQueue



配列全体が完了するまで現在のスレッドをブロックすることができます。



Operation Queue OperationQueue



は、優先度qualityOfService



、「準備完了」( isReady



プロパティisReady



true



設定されてtrue



)、および他の操作からの依存関係に従って、配置された操作を実行しtrue



。 これらすべての特性が等しい場合、操作はキューに入れられた順序で「実行」のために送信されます。 操作が操作キューに配置されると、これらのキューのいずれにも再び配置できません。 操作が実行された場合、操作のキューで繰り返し実行することはできません。操作は1回限りです。したがって、 Operation



クラスのsubclasses



を作成し、必要に応じて使用して、この操作のインスタンスを取得します。



たとえば、アプリケーションがバックグラウンドモードで「離れる」場合、 cancellAllOperations ()



メソッドを使用して、キュー内のすべての操作にcancel()



メッセージを送信できます。 waitUntilAllOperationsAreFinished()



メソッドを使用して、このキューに対するすべての操作が完了するまで現在のスレッドをブロックできます。 ただし、 main queue



これを実行しないでください。 すべての操作を完了した後にのみ何かを行う必要がある場合は、操作のprivate



シリアルキュー( serial queue



)を作成し、そこで操作の完了を待ちます。



OperationQueue操作キューはDispatchGroup



ように動作します。 OperationQueue



でさまざまなqualityOfService



して操作を追加できます。それらは優先度に従って起動されます。 また、操作キュー全体に対してqualityOfService



をより高いレベルで設定することもできますが、この値は単一操作のqualityOfService



の値によって再定義されます。



OperationQueue



のデフォルトのqualityOfService



.background



です。



isSuspended



プロパティをtrue



設定して、 OperationQueue



操作を停止することもできtrue



。 このキュー内の操作は続行されますが、 isSuspended



プロパティの値をfalse



変更するまで、新しく追加された操作は実行のために送信されません。 isSuspended



プロパティのデフォルト値はfalse



です。



Playground



Operation操作とOperation



キューでいくつかの実験をしてみましょう。



実験1. OperationQueue



を作成してクロージャーを追加する



コードはGithubの OperationQueue.playground



で表示できます。



空のprinterQueue



作成しprinterQueue













クロージャーの形式で操作をprinterQueue













printerQueue



を追加するとすぐに、操作は現在のスレッドに対して非同期に開始され、 ready



状態になります。 waitUntilAllOperationsAreFinished()



メソッドを使用してすべての操作の実行時間を推定します。このメソッドは、現在のスレッドに対して「同期的に」操作が完了するまで待機します。 アプリケーションのmain queue



では、これを行わない方が良いですが、 Playground



U



I Playground



何も起こらずPlayground



余裕があります。 7つの操作すべての合計実行時間は2秒よりもわずかに長く、 sleep(2)



演算子の実行時間に対応するため、 printerQueue



は多くのスレッドで7つの操作すべてを同時に開始します。



printerQueue



キューprinterQueue



変更し、 maxConcurrentOperationCount



プロパティを2に設定しましょう。







ペアで開始し、最後の7番目の操作が4番目の「ペア」の1つを開始するため、すべての操作をprinterQueue



に置くのに非常に短い時間がかかり、すべての操作を完了するのに8秒強かかります。



次に、 qualityOfService



.userInitiated



増やした別のconcatenationOperation



操作を追加し.userInitiated











最初の機会に、優先度の高い操作が他の操作よりも早く実行されますが、すべての操作の合計実行時間は実質的に変わらないままで、8秒強です。



maxConcurrentOperationCount



プロパティを1に設定して、 maxConcurrentOperationCount



serial



maxConcurrentOperationCount



ましょう。







この場合、操作は順番に実行され、優先順位が機能せず、すべての操作の合計実行時間が16秒に増加することがわかります。



フィルター処理されたソース画像の配列を取得する場合、より複雑なケースを検討







前のセクションでおなじみのカスタムFilterOperation



操作が使用されます。







フィルター操作用のfilterQueue



、フィルター処理されたイメージを配列に追加するスレッドセーフ用のappendQueue



シーケンシャル( serial



)キューを作成します。 実際には、多くのフィルタリング操作がshared



リソース( filteredImages



配列)に同時にアクセスして、要素を追加します。 覚えてる? shared



リソースを変更するためにserial



DispatchQueue



キューを使用しましたか? これも同じですが、 Operation



に対して実行されます。 もちろん、シリアル( serial



DispatchQueue



キューの方が効率的ですが、シリアル( serial



OperatioQueue



キューの作成と使用を示します。



Swift



配列はvalue type



あり、コピー時にコピー( copy on write



)されるため、マルチスレッドの変更について心配する必要はありませんが、特に配列が型クラスのオブジェクトのプロパティである場合、これに問題があるかどうかについてフォーラムにメッセージがあります。 したがって、異なるスレッドで新しい要素を追加するときにthread safe



配列を保持する方法を示します。







次に、各画像のフィルタリング操作を作成し、非同期実行のためにfilterQueue



に追加します。 フィルター処理が完了したら、結果のイメージをfilteredImages



配列に追加し、 completionBlock



appendQueue



ます。ここで別の操作キューappendQueue



を使用します。 completionBlock



入力パラメーターcompletionBlock



なく、何も返しません。



すべてのフィルタリング操作が完了するのを待って、フィルタリングされた画像配列を確認します。







すべての操作の実行時間は1.19秒です。これは、1つの操作(前のセクションを参照)の実行時間に匹敵します。 filterQueue



filterQueue



キューで操作のマルチスレッド実行が行われます。 Playground



は、フィルタリングされた画像の配列が表示されますが、フィルタリング効果を見るには非常に小さいです。 クイックビューボタンをクリックして、フィルタリング効果を確認できます。







非同期操作



コードはGithubの AsyncOperations.playground



で表示できます。



これまでのところ、SYNCHRONOUSタスクの操作、つまり現在のスレッドを使用し、タスクが完了するまで戻らない関数を使用しました。 ASYNCHRONのタスク(関数)はまったく異なる方法で動作します:現在のスレッドですぐに制御を返し、別のスレッドでタスクを実行し、タスクが完了したことを知らせるため、他のスレッドでcompletionHandler



が閉じられます。 典型的な例はURLSession



です:







Operation操作でURLSession



の機能を「ラップ」できますが、操作のステータスを手動で制御する必要があります。



SYNCHRONOUS関数のカスタム操作を作成するには、操作メソッドmain()



をオーバーライドするだけで済みました。 ASYNCHRON関数でまったく同じことを行う場合、 main()



ですぐに制御を現在のスレッドに戻し、別のスレッドで動作するために「脱退」し、 main()



メソッドの最後に移動し、 OperationQueue



すぐにOperationQueue



キューから「スロー」します。 ASYNCHRON機能を完了することはありません。 これがOperationQueue



背後にあるロジックです。



非同期操作には、まったく異なる作業ロジックがあります。







操作が「準備完了」( isReady = true



)の場合、OperationQueue操作キューはstart()



メソッドを呼び出します。このメソッドでは、操作を「running」状態( isExecuting = true



)に設定し、 main()



メソッドを呼び出します。このメソッドはASYNCHRON関数を呼び出します。 ASYNCHRON関数はANOTHERスレッドで何かを行いますが、 isExecuting



プロパティは、CURRENTスレッドで何も行わず、別のスレッドで実行されているタスクのみを表す場合でもtrue



ままでなければなりません。 ASYNCHRON関数がASYNCHRON関数のcompletionHandler



を示すcompletionHandler



呼び出す場合、 completionHandler



プロパティisFinished



true



に、 isExecuting



プロパティをfalse



設定する必要がありfalse







したがって、ASYNCHRON操作の場合、 main()



メソッド以上のものをオーバーライドする必要があります。 次のメソッドとプロパティをオーバーライドする必要があります。







Operation



から継承され、ASYNCHRONOUS操作の実行に適した抽象カスタムクラスAsyncOperaton



作成しましょう。 その抽象性は、非同期操作を実行するmain()



メソッドがないことです。 彼の図は次のとおりです。











OperationQueue



なしで自分でASYNCHRONOUS操作を使用する場合、 isAsynchronous



プロパティをオーバーライドしてtrue



を返す必要があります。 ASYNCHRON関数を実際に開始し、 isExecuting



プロパティをtrue



保つには、 start()



メソッドをオーバーライドする必要があります。 また、操作の状態を決定するプロパティisReady



isExecuting



isFinished



を管理する方法を学ぶ必要があります。 これらのプロパティは、 OperationQueue



操作キューによって使用され、 OperationQueue



のステータスを監視し、依存する操作の実行を整理します。



操作間の依存関係を定義する場合、1つの操作が別の操作を開始する前に終了する必要があるため、 OperationQueue



操作キューは、操作がいつ終了するかを知ることが重要です。 SYNCHRONOUS操作の場合、SYNCHRONOUS機能が終了するとSYNCHRONOUS操作が終了するため、これは問題ではありません。 ただし、ASYNCHRON関数は現在のスレッドの外側で終了するため、ASYNCHRON関数の実際の終了についてOperationQueue



操作キューに通知する方法が必要です。 GCD



を思い出すと、ASYNCHRONOUS関数をグループに追加するときに、 enter()



およびleave()



メソッドを使用して、ASYNCHRONO関数の開始と終了を明確に示しました。 ただし、Operation操作の場合、操作にはisReady



isExecuting



isFinished



isCancelled



などの状態があるため、状況ははるかに複雑ですisReady



関数をOperation操作に追加する場合、これらの状態を手動で管理する必要があります。 この作業を容易にするために、 AsyncOperaton



という名前のOperation



クラスの特別な抽象ユーザーsubclass



を作成しました。その主なタスクは、操作の状態の変更を制御することです。 独自のASYNCHRON関数の場合subclass



クラスのsubclass



作成し、そこからASYNCHRON関数を呼び出すmain ()



のみを定義します。 そして、すでにこの新しい操作はOperatonQueue



に追加されOperatonQueue







ただし、問題は、たとえば、操作の状態に関連するすべてのプロパティisReady



isExecuting



isFinished



readonly



{get}



)であり、直接設定できないため、 isExecuting = true



記述できないこと{get}



AsyncOperaton



操作のステータスAsyncOperaton



変更されたことをシステムに通知しながら、 isExecuting



プロパティがtrue



返すようにすることしかできません。



Operation



クラスは、 KVOkey-value observation



)メカニズムとwillChangeValueForKe



およびdidChangeValueForKey



isReady



isExecuting



isFinished



isExecuting



などの状態プロパティの変更について通知します。



したがって、資産管理業務の便宜のために、私たちは、新しいデータ型を作成します-適切な列挙enum State



非同期操作独自の変種の状態を表すために:ready



executing



finished













列挙State



には、KVO通知のスイッチとして使用fileprivate



する名前のプロパティも含まれます。プロパティは計算され大文字の列挙要素の名前であるに接続された文字列で構成されます。次に、抽象クラスで操作の現在の状態を表す変数を定義しますデフォルトでは、この値はequal です。変数を変更するたびに、KVO通知を切り替える必要があります。これを行うには、オブザーバープロパティを使用ますkeyPath



keyPath



"is"



rawValue



State







AsyncOperaton



var state



State



ready



state



willSet {}



didSet {}



state













state



たとえば、からexecuting



操作状態を切り替える前に、両方の操作の新しい状態)と現在の状態についてKVO通知finished



を送信する必要があります。切り替え条件が後に発生した、我々は送信KVOの通知をするために両方の条件:)と)。これは、システムが新しい値「ネイティブ」の状態変数の読み出し動作をするようになります。そのため、非同期操作我々は「ネイティブ」状態変数の操作を再定義する必要がありwillChangeValue



newValue



finished



state



executing



state



didChangeValue



keyPath



oldValue



executing



state



finished







isReady



isExecuting



isFinished



AsyncOperation



isReady



isExecuting



isFinished



state



正しい値を返す計算変数として新しいプロパティを使用します。これをextension



クラス拡張で行いAsyncOperation



ます:









ある時点で、操作のプロパティisReady



が等しくなりtrue



、プロパティisReady



forを使用する必要があります。これは、他の操作superclass



への依存性(dependencies



を「知覚」します。独自のロジックとのプロパティisReady



組み合わせるsuperclass



ことで、操作が本当に「準備完了」であることを確認できます。注変数があればそれstate



.finished



、のプロパティsuperclass



isFinished



と同じtrue



、とプロパティisExecuting



- false



また、2つのメソッドをisAsynchronous



返すことでプロパティを再定義しますtrue



start()



cancell()







メソッドでstart()



は、操作が破棄されているかどうか、つまりプロパティisCancelled



がequalであるかどうかを確認しますtrue



。その場合、変数に新しい値を設定する必要がありますstate



- .finished



。操作が破棄されない場合、を呼び出しますmain()



。それは覚えているmain()



非同期関数であり、それはすぐに戻りますので、我々は手動操作の状況を設定する必要がstat



同じです.executing



。 ASYNCHRON関数が戻るとき、completionHanller



操作の状態を設定しなければなりません.finished



。 ASYNCHRON関数の場合、start()



メソッドでsuperclass



-の同様のメソッドの呼び出しを使用できないことを覚えておくことが非常に重要です。これはsuper.start()



、関数の同期起動を意味し、main()



正反対が必要だからです。

このメソッドでcancell()



は、操作の状態も設定する必要があります.finished







その結果、抽象クラスを取得しましたAsyncOperation



、独自の非同期操作に使用できます。これを行うには、次の手順を実行します。









例として、sleep (1)



2つの数値の非同期低速(内部にある)加算の機能を使用します。









AsyncOperation



as を使用しsuperclass



、再定義main ()



し、操作状態state



.finished



in に設定することを忘れないcallback



ください:









callback



リターンの結果result



、我々はプロパティ操作割り当て、self.result



およびセット動作状態のstate



時に.finished



ことを通知番号の非同期添加終了のすべての操作をして、もはやこの操作で作業する必要があります。



を使用してSumOperation



、数値のペアの合計の配列を取得します。









数値のペアごとSumOperation



に、additionQueue



通常の方法で操作を作成し、操作のキューに配置します非同期操作の順序は、配列内の数値のペアの順序とはわずかに異なることがわかります。これは、非同期操作のマルチスレッド実行を示しています。



第二の例は、与えられた画像の非同期ローディングに関するURL



介しURLSession











非同期動作作成ImageLoadOperation



前回のようにクラスのサブクラスです、、 AsyncOperation



操作についてはImageLoadOperation



、前回のように、再定義main ()



、および操作のステータスを設定することを忘れないでくださいstate



.finished



completion











操作を作成しoperationLoad



、画像を取得operationLoad.outputImage



し、上に表示view











コードは、上で閲覧することができますAsyncOperations.playground



のGithub



依存関係(dependencies





コードはGithubの LoadAndFilter.playgroundで表示できます



このセクションでは、1つの操作の結果を別の操作に転送し、最初の操作が終了したときにのみ2番目の操作を強制的に開始する方法を検討します。







図では、2つの操作が表示されます。1つ目はネットワークから画像を読み込み、2つ目は画像の上部と下部の「フォギング」を実行します-フィルタリング。

これらの操作は両方とも相互に非常にうまく機能します。イメージの「ダウンローダー」はそのデータを「フィルター」に直接転送します。



これらの両方の操作を含む1つの操作を作成できますが、これは非常に柔軟な設計ではありません。







アプリケーションのさまざまな場所でイメージを任意の順序で使用するために、イメージを操作する際に、操作のモジュール性をある程度確保することが望ましいです。



より柔軟なソリューションは、最初の操作から2番目の操作へのイメージの転送を伴う操作の「チェーン」の実装に関連付けられています。 「フィルター」は「ローダー」に依存していると判断でき、操作のキューはOperationQueue



、「フィルター」がready



「ローダー」の終了後にのみ状態に設定されることを認識します。これは本当に操作キューのすばらしい「能力」ですOperationQueue



。 「依存関係」の非常に複雑なグラフを作成して、OperationQueue



必要に応じて自動的に操作を開始することができます。「依存関係」の処理をサポートAPI



するクラスOperation



dependencies



)-非常にシンプルですが、操作中に素晴らしい力を発見します。







「依存関係」(dependency



を追加および削除したりdependencies



、この操作に追加された「依存関係」のリストを取得したりできます。以下では、リストdependencies



使用して、依存フィルタリング操作の入力画像を取得します。



依存関係を作成すると、デッドロックdeadlock発生する可能性が高くなり







ます。「依存関係」列に閉じたループが現れると、デッドロック発生し、視覚的な分析によって特定する以外に、それらを排除する普遍的な方法はありません。

操作の「依存性」に関連する次の問題は、「依存性チェーン」に沿ってデータを転送する方法です。たとえば、上記の例では、「ローダー」が最初に機能し、次に「フィルター」が機能する場合、入力イメージは「ローダー」の出力イメージになります。











これをどのように達成しますか?このImagePass



場合、必要なデータを提供するプロトコルを作成しますUIImage?











ローダー「 -すでにおなじみのクラスのImageLoadOperation



彼は与えられた入り口にはネットワークのイメージロード操作。URL



文字列などの画像urlString



、および出力-イメージそのものoutputImage











クラスImageLoadOperation



」確認「プロトコルImagePass



とプロトコルの特性を返すimage



出力は画像を読み込まoutputImage







ターンでは、操作」フィルタリング「 -クラスをFilterOperation



-入力画像が存在しない場合にはさ_inputImage



依存性の確認」「dependencies



とだけ人々に興味を持っている」確認「レポートImagePass



彼は依存として第1の動作を選択し、抽出物がそこ。inputImage











」フィルタ「を入力で-入力画像inputImage



、出力は関数を使用してフィルター処理されたfilterImage (image:)



画像outputImage



です。これは通常の同期操作なので、再定義するだけで済みますmain()







「フィルター」inputImage



が「ロード」操作の出力イメージを入力イメージとして使用するように、これら2つの操作を連携させたいと思いますこれを行うために、我々は、フィルタリングの動作を設定しfilter



た値でnil













コードが上に配置されているLoadAndFilter.playground



のGithub



上の操作の破壊 OperationQueue





コードが上に配置されているCancellation.playground



のGithub



キューの別の素晴らしいOperationQueue



機会を見てみましょう-オペレーションを破壊する能力。



操作Operation



をキューに配置した後は、操作OperationQueue



実行するための独自の計画があり、操作を完全に制御するため、操作を実行する方法はありません。ただしOperation



、メソッド使用して破棄することができますcancel()











メソッドを呼び出すcancel()



とすぐに操作が停止すると思うかもしれませんが、そうではありません。このメソッドcancel()



は、isCancelled



操作プロパティをに設定するだけtrue



です。操作がまだ開始されていない場合、デフォルトの方法start()



操作の実行を許可せず、プロパティisFinished



をに設定しますtrue



。あなたは(オーバーライドする場合override



)の方法をstart()



、あなたはあなたの能力保存しなければならないstart()



財産の取引があれば、操作の実行を防止するためisCancelled



に設定されているがtrue



。そして、抽象クラスを見ると、AsyncOperation



まさにそれを行っていることがわかります。



さらに、main()



操作メソッドで、特に低速またはリソース集約的な処理を実行する前にisCancelled



、操作が既に破棄されているかどうかをプロパティテストする必要があります。操作が破棄され、true



プロパティの値が表示された場合、操作isCancelled



を停止するために必要なアクションを実行する必要があります。操作の場合Operation



たとえば、画像変換などのローカルで実行した場合、操作を停止できます。サーバーから画像をダウンロードするなど、ネットワークへのアクセスに関連する操作の場合、サーバーが結果を返すまでそのような操作を停止することはできません。



操作のさまざまな「ステップ」の間に「ロジック」を追加して、この操作を実行し続ける価値があるかどうか、または操作をisFinished



等価状態に設定する価値があるかどうかを確認する必要がありtrue



ます。操作を削除するように設計され



API



たクラスOperation



は非常に単純で、2つの位置のみで構成されます。









メソッドを呼び出すcancel()



- isCancelled



操作プロパティをに設定true



ます。メソッドが呼び出されたときにことに注意することが重要でありcancel()



、プロパティisExecuting



isFinished



もで変化false



し、true



それぞれ。









操作がisCancelled = true



終了せずに破壊されることは完全に正常ですisFinished = true



プロパティisCancelled



は、停止する必要があることを操作に伝え、プロパティは、isFinished



操作が既に停止していることをシステムに伝えます。



私たちの抽象非同期操作は、AsyncOperation



メソッドをオーバーライドしcancel()



、動作状態を設定する方法にstate



.finished



て、このような変更は、変更操作のプロパティを起こしisFinished



isExecuting













キューOperationQueue



はすべての操作を破棄できます









例としていくつかのカスタム操作を使用して、メソッド呼び出しに正しく応答する操作を取得する方法を見てみましょうcancel()







コードが上に配置されているCancellation.playground



のGithub



操作にArraySumOperation



は、inputArray



整数のペアで構成されるタプルの入力配列がありoutputArray



、出力それらの合計の配列を形成します。







数値の各ペアに対して、Cancellation.playgroundslowAdd



のフォルダーSource



ある「遅い」加算関数を使用し、出力配列に追加します数値の入力配列を設定し、操作を形成し、操作キューに追加しますoutputArray







sumOperation



queue



タイマーを起動しsumOperation



ますcancel()



。これにより、メソッド呼び出しに対する操作の反応を確認できる時間をさらに調整できます。さらに、操作にはcompletionBlock



、タイマーを停止しPlayground



outputArray



、操作をポイントて終了しますPlayground











そのため、操作を完了するにはsumOperation



5秒以上かかります。今呼び出すことによって開始した後、2秒後に、この操作を破壊しようcancel ()











私たちは、予期しない結果を得ました-操作がsumOperation



完全に無破壊操作が発生していない、実装されています。問題は何ですか?しかし、実際には、メソッドcancel ()



はプロパティisCancelled



true



設定するだけであり、操作の削除に必要なアクションは操作の開発者に委ねられます。プロパティがにisCancelled



設定されているという事実に対応する必要がありますtrue



。出力配列に合計を追加する前に、操作が破棄されるかどうかを確認します。そして、それが破壊された場合、サイクルを中断します:







再開しましょうPlayground











2秒より少し遅れて停止し、2つの量を取得しました.3番目の量を取得しようとしたときに、操作の破壊に関するシグナルを取得し、量のさらなる受信を停止しました。この例は、コマンドに対するユーザー操作の応答を取得する方法を明確に示していますcancel ()







タプル配列を介して出力配列ループを取得するためにAnotherArraySumOperation



別の関数が使用される点で異なる別の操作を見てみましょうslowAddArray











前のケースとの違いは、タプル配列の要素を介したループがmain()



操作メソッドではなく、別の関数であり、私たちにとって難しいことです操作が破棄された場合、サイクルを中断します。しかし、非常に洗練されたものではありますが、このような可能性があります:







関数の入力には、整数slowAddArray



input



ペアの配列に加えて、引数がありますprogress



。これはOptional



、配列の処理の深さを引数として取り、



Double(results.count) / Double(input.count







それを返す関数ですBool



。これBool



により、アレイの継続処理が決定されます。



方法でmain()



操作AnotherArraySumOperation



(前の図)では、関数slowAddArray



配列を渡しました。inputArray



引数はprogress



、「テール」クロージャーとして設計されており、progress



印刷にプロパティを使用しました。プロパティprogress



Double



ですので、100を乗算し、配列処理の完了率と操作の完了率を取得しました。次に、操作の破棄に対する応答を返します。これは、配列の処理を続行または中断する信号です。反応はプロパティの逆ですisCancelled







直前の操作を元に戻しSumOperation



、新たな操作上AnotherArraySumOperation











非継続事業をした後2秒、我々は同じ結果を得た-我々だけ扱うことができた2の配列に5- 操作が破棄される前、つまり40%



操作中断遅延を4秒に設定します。5の配列の4つの要素、つまり80%は、操作が破棄さ







れる前に処理されました。個々の操作がプロパティに応答するため、破棄できることを確認することが非常に重要です。しかし、この方法を使用して、個々の取引の破壊に加えて、あなたはキュー操作にすべて開始の操作を削除することができます方法を使用して



isCancelled







cancel ()



OprationQueue



cancellAllOperations



これは、単一の目的で機能する一連の操作がある場合に特に役立ちます。この目標は、複数の独立した操作を並行して実行すること、または次々に実行される依存操作のグラフにすることです。これらの両方のケースを検討します。



パターン1.独立した操作のグループと連携する



コードが上に配置されているCancellationGroup.playground



のGithub前のセクションで示し



た操作と同じ結果が得られるようにタスクを設定ArraySumOperation



しました。この操作はタプルの配列を受け取り、(Int, Int)



スロー加算関数を使用しslowAdd()



て、タプルを構成する数値の合計の配列を作成します。入力配列のコンポーネントのサイクルは内部に隠されていArraySumOperation



ます。個別の非常に単純な型操作のグループを作成しましょうSumOperation



。操作は、SumOperation



入力ペアの2つの数値を追加しinputPair



、低速加算機能を経由してslowAdd()



、結果を返しますoutput











普通のクラスを作成GroupAdd



管理private



運営のキューqueue



と一連の操作をSumOperation



入力配列のすべてのペアの合計を計算し、出力配列にoutputArray



タプルを配置するためにInt, Int, Int )



ソースデータと結果で構成され







ますクラスのインスタンスを初期化するとき、数値GroupAdd



input



ペアの入力配列が形成され、そこから型の演算が形成されSumOperation



ます。completionBlock



各演算で、結果が出力に追加されますアレイのoutputArray



単一上で実行され、private



シリアルライン操作appendOperation



を避けるために競合状態を



クラスは、すべての一般的な操作があるOperation



メソッドを:start()



cancel ()



wait ()



。、私たちは、「複雑な操作」としてそれを考えることができるように



作成します。EQ emplyarクラスGroupAdd



数値のペアの配列を入力に送ります:







startを実行しgroupAdd



1待機してcancel ()



から、メソッド使用してすべての加算操作を操作キューから削除します。結果として、すべての動作(使用の完了後wait()



に使用することができないmain queue



が、上にあることができるPlayground



)、我々は、出力配列を短縮:







結果は、上で閲覧することが可能Playground



CancelletionGroup.playground



のGithub



パターン2.依存する操作のグループと連携する



コードが上に配置されているCancellationFourImages.playground



のGithub



依存する操作のグループとして、既によく知られている相互接続された操作のグループを考えてみましょうUI



ネットワークからのイメージの読み込み、フィルタリング、変更です。クラスでシーケンスを並べるようにしてくださいImageProvider



にこれらの操作を管理しますOperationQueue



メソッドを使用してstart ()



wait ()



cancel ()







2つの抽象操作(つまり、メソッド実装を持たない操作main()



)があります。 1つはすでに慣れ親しんでいるASYNCHRONOUS操作であり、もう1つは依存関係から入力イメージAsyncOperation



ImageTakeOperation



抽出する操作ですに基づいてinputImage



dependecies







AsyncOperation



指定されたURLの「ネットワーク」から画像をダウンロードする操作を作成しましょう







この操作ImagePass



は、受信した画像outputImageを操作のチェーンに沿ってさらに送信するためのプロトコル確認します



抽象操作は、この操作の初期化中に指定されていない場合、依存関係からImageTakeOperation



入力イメージinputImage



抽出し、シーケンシャル操作のチェーンでイメージを転送するために使用されるdependecies



既知のプロトコルを使用して出力イメージを「ピックアップ」できますImagePass











抽象クラスImageTakeOperation



superclass



操作として使用する非常に便利です依存操作のチェーンに関与します。たとえば、フィルター操作の場合Filter











または、「ヒップスター」スタイルで画像を「エージング」する操作の場合PostProcessImageOperation











または、クロージャーを使用して環境に入力画像を「捨てる」操作の場合ImageOutputOperation











クラスを見てみましょうImageProvider



。通常のクラスを作成してImageProvider



制御することをprivate



キュー操作operationQueue



、および一連の操作dataLoad



filter



およびoutput



与えられたから画像をロードするためのURL、フィルタ、それを回路に渡しcompletion











クラスはImageProvider



、すべての典型的な動作の持つOperation



メソッドを:start()



cancel ()



wait ()



、私たちは、「複雑な操作として、それを考慮することができます「。



4つのクラスインスタンスを作成しますImageProvider











イメージのロードを開始します:







操作の完了を待機しており、4つのイメージを取得します:







すべての操作継続時間は10秒をわずかに超えています。



イメージのロードを開始し、6秒待ってcancel ()



から、メソッドを使用してすべての操作を削除します。その結果、1番目、3番目、4番目の3つの画像のみがダウンロードされます







。結果はGithubのプレイグラウンドCancelletionFourImages.playground



表示できます



パターン3. TableViewControllerとCollectionViewControllerの操作



プロジェクトコードはGithubのフォルダーOperationTableViewController



あります多くの場合、iOSアプリケーションのテーブルには画像が含まれており、その画像を受信するにはサーバーへのアクセスが必要です。また、前のセクションで説明した「フィルタリング」などの受信画像に対する追加アクションが含まれる場合があります。これにはかなりの時間がかかり、テーブルをスムーズにスクロールするには、画像を使用したすべての操作を外部で非同期に実行する必要があります。前のセクション示したクラスのアプリケーションを見てみましょう。これは、ネットワークからイメージをロードし、それをフィルタリングおよび変更するための相互接続された操作のグループにすでに馴染みのあることを実行します。例として、1つだけで構成される非常に単純なアプリケーションを考えます



main queue



ImageProvider



UI







Image Table View Controller



、テーブルセルは、インターネットからダウンロードされたイメージとブートプロセスを示す活動の指標が含まれているどの:







こちらのクラスであるImageTableViewController



スクリーンのサービスフラグメントImage Table View Controller











クラスのモデルはImageTableViewController



8つの配列ですのURLを



  1. エッフェル塔
  2. ヴェネツィア-他のものよりもはるかに長くロードおよびフィルタリング
  3. スコットランドの城
  4. 北極-02
  5. エッフェル塔
  6. 北極圏-16
  7. 北極-15
  8. 北極-12








ImageTableViewCell



画像がロードされるテーブルのセルのクラスは次のとおりです。







Public API



このクラスは、画像のURLアドレスをimageURLString



含む文字列です。しかし、等しくないように設定する、画像のロードは行われず、「回転ホイール」の形のインジケータのみが機能し始めます。ただし、既に何らかの方法でロードおよび処理されたイメージがあり、methodを呼び出している場合、ライトアニメーションを使用して画面上のこのセルに表示します。このクラスには、メソッドに値を割り当てると開始するアクティビティインジケータがあります画像は、セルとテーブルの相互作用を担当するデリゲートメソッドにロードされます。imageURLString



nil



image



updateImageViewWithImage



spinner



imageURLString



tableView( _ : cellForRowAt:)







UITableViewDelegate



UITableViewCell



UITableView















このメソッドを可能な限り「促進」するために、クラスImageProvider



使用した非同期イメージの読み込みの要求はメソッドの範囲外になりますtableView( _ : cellForRowAt:)



tableView( _ : willDisplay:forRowAt:)



デリゲートメソッドに配置され、セルが表示されるように準備します。別のtableView( _ : didEndDisplaying:forRowAt:)



デリゲートメソッドは、セルが画面を離れるまでに完了しないイメージのダウンロード要求を破棄するために使用されます。これはかなり一般的なアプローチであり、動作するアプリケーションで使用できますTableView



。これにより、テーブルのスクロールパフォーマンスが向上します。



しかし、最初に、ImageProvider



このアプリケーションで使用されるクラスに戻ります。ImageProvider



前のセクションで使用されたクラスバリアントとは対照的Playground



、簡略化された形式を使用します。つまり、クラスのインスタンスを初期化するときImageProvider



isSuspended = true



操作キュー停止してからImageProvider



、メソッドを使用してクラスインスタンス具体的に開始する必要はありません。start()



初期化中に依存操作のチェーンをすぐに開始し、waitUntilFinished



等しく設定します。false



これはPlayground



アプリケーションではなく、同期メソッドを使用しますwait()











したがって、クラスImageProvider



にはイニシャライザがあり、その入力に、画像imageURLString



URLとクラスのこのインスタンスを作成した人に型の画像completion



generic



返すClosure を含む文字列を指定する必要がありますUIImage?



ImageProvider



、計算されたプロパティを使用する代わりにimage



。入力回路completion



には署名があります(UIImage?) -> ()



。つまり、画像UIImage?



を取得し、何も返しません。に戻るために使用できますUITableViewController







さらに、クラスのインスタンスの破棄を許可する必要があります。これにより、ImageProvider



すべての操作が完了する前にテーブルセルが画面を離れると、開始されたすべての操作が破棄されます。したがって、cancel()



クラスにメソッドがありImageProvider



ます。



そのため、このクラスImageProvider



は、サーバーからのイメージのロード、フィルタリング、およびメソッドへの配信に関する依存操作のグループの非同期実行を提供しますUITableViewDelegate



。必要に応じて、このクラスのインスタンスを削除できます。



に戻るImageTableViewController







メソッドtableView( _ tableView:, cellForRowAt indexPath:)



画像をロードする代わりに、別のデリゲートメソッドtableView( _ tableView: , willDisplay cell:, forRowAt indexPath:)



でそれを行います- そして、メソッドの画像を削除しますtableView( _ tableView: , didEndDisplaying cell:, forRowAt indexPath:)



これら2つのメソッドの



拡張機能extension



作成しましょう。メソッドから始めましょうtableView( _ tableView: , willDisplay cell:, forRowAt indexPath:)











メソッドのtableView( _ tableView:, cellForRowAt indexPath:)



ように、セルcell



indexPath



。まず、標準の手順に従って、セルのタイプがであることを確認しImageTableViewCell



ます。次に画像URLとしてのimageProvider



文字列imageURLs [ (indexPath as NSIndexPath).row ]



と、「テール」クロージャーとして設計されたクロージャーを作成します。クロージャーでは、このテーブルセルに表示する必要がある画像を取得します。これは、更新に使用する必要がありますimage



UI



main queue



なぜならUI



、バックグラウンドキュー(background queue



)で更新を行おうとすると、これは機能せず、なぜこの画像が表示されないのか不思議に思うからです。main



クラスプロパティがありますOperationQueue



main queue



必要なセルを更新するメソッドupdateImageViewWithImage( image )



呼び出すmain queue



だけUITableViewCell



です。



ここで、操作の削除の可能性について考える必要があります。これを行うには、作成されたものへのリンクを失う必要はありませんimageProvider



。そうしないと、後でそれを見つけて、それに関連付けられている操作を削除できません。



私たちは、クラスの先頭に移動ImageTableViewController



し、Cという名前の新しいプロパティを追加imageProviders











プロパティは、imageProviders



型のオブジェクトの集合でありimageProvider



、最初は空です。



ImageProvider.swiftファイルの下部を見てみましょう。あなたは、既存の拡張すでにそこにあるでしょうextension



クラスImageProvider



プロトコルを確認し、Hashable



セットに必要なのSet











私たちは、計算プロパティを取得hashValue



と平等のための比較演算子を==



。これで、オブジェクトのインスタンスをインストールして比較できますImageProvider



。に戻るImageTableViewController



。これで、オブジェクトのインスタンスを追跡ImageProvider



し、imageProviders



現在アクティブなセットにそれらを追加できます。







このコードを見て、何が起こっているかを段階的に確認してみましょう。これはデリゲートメソッドですtableView



、セルが画面に表示される直前に呼び出されます。この時点で、ImageProvider



非同期的に読み込み、画像をフィルタリングし、結果の画像image



をに返すものを作成completionHandler



ます。のimage



更新UIImageView



使用main queue



ます。その後、後で破壊できるようImageProvider



に、多数で記憶imageProviders



ます。これは、次のデリゲートメソッドtableView



tableView( _ tableView:, didEndDisplaying cell:, forRowAt indexPath:)



、セルが画面を離れた直後に呼び出される名前で行います。それは私たちがこのすべての操作を破壊する必要があることをここにあるImageProvider











これを行うには、私たちが見つけImageProvider



セル用cell



及び方法を使用cancel ()



するにImageProvider



、このプロバイダーのすべての操作を削除してから、プロバイダー自体をsetから削除しますimageProviders



いつものように、最初に標準手順を実行して、セルのタイプがであることを確認してから、このセルImageTableViewCell



と同じ行を持つすべてのプロバイダーを見つけますImageURLString



これらすべてのプロバイダーを調べて削除し、セットから削除しますimageProviders



それが私たちがする必要があるすべてです。



アプリケーションを実行しましょう。







画像の読み込み中にアクティビティインジケータが動作し、スクロールが遅延なく非常に高速に動作するようになりました。画像はロードされるまで空で、アニメーション化されます。素晴らしい。

プロジェクトコードはGithubのフォルダーOperationTableViewController



あります



Operation



OperationQueue



GCD





GCD



そしてOperations



、彼らがどのように異なるかが、表ショーでは、似たような機能の多くを持っています。







DispatchGroup



そして、OperationQueue



それらはすべてのタスクの完全な完了に関連するイベントを処理できますがwaitUntilAllOperationsAreFinished



、キューのメソッド開始するときは非常に注意する必要がありOperationQueue



ますmain queue







依存関係(dependecies



)に関してGCD



は、タスクのチェーンをprivate



順次(serial



に実装するだけDispatchQueue



です。しかし、これは最も強い側面OperationQueue



です。依存関係(dependecies



OperationQueue



は単なるチェーンよりも複雑になる可能があり、異なるキューで操作を実行できますOperationQueue







でバリアを使用できますGCD



シーケンシャル(serial



)キューがDispatchQueue



適切ない場合の「ライター」と「リーダー」の問題を解決する。を使用してこの問題を解決する適切な方法はOperationQueue



非常に紛らわしく、flags



非常に特別な依存関係が必要です。



GCD



のみ削除できDispatchWorkItems



ます。Operations



独自のメソッドを使用して操作を削除することもcancel()



、すべての操作をすぐに削除することもできますOperationQueue



。で短絡を削除できBlockOperation



ます。



との両方GCD



Operations



、SYNCHRONOUS機能を非同期的に実行できます。そうすることで、この操作Operation



は、実装を含め、この再利用可能な機能のすべてのデータをカプセル化するためのオブジェクト指向モデルを提供しますsubclasses



Operation



。しかし、多くの場合、複雑な依存関係を持たない単純なタスクの場合はGCD



、操作を作成するよりも「軽い」メソッドを使用する方が便利Operation



です。さらに、Dispatch



ブロックのGCD



完了要する時間は、ナノ秒からミリ秒までで、Operation



通常は数ミリ秒から数分かかります。



おわりに



この記事では、操作に関する以下の質問を考慮し、Operation



かつキュー操作OperationQueue











1.操作がOperation



問題と「ライフサイクル」とそのステータスを表示するプロパティを持つ1つのオブジェクト内のデータをカプセル化することができます。



2. BlockOperation



オブジェクト指向の「ラッパー」DispatchQueue.global()



であり、そのグループの制御を失うのではなく、クロージャーのグループの実行を追跡できますDispatchQueue.global()



すでにアプリケーションで使用していて、それらに干渉したくない場合はBlockOperation



、代替として単純な操作に使用すると便利です3.運用が開始されると、その主な可能性が明らかになりますGCD



Operations



DispatchQueue







Operation



OperationQueue



。操作を準備したらOperation



、それをOperationQueue



渡します。これは、すべての操作の実行順序を制御します。これは、本質的に非常に単純なモデルであり、マルチスレッドプログラミングの複雑さを隠します。OperationQueue



に似てDispatchGroup



おり、異なるレベルの操作を組み合わせてqualityOfService



、すべての操作が完了するまで待つことができます。ただし、このsync



メソッドを呼び出すときは非常に注意する必要があります



4.操作にASYNCHRON関数を含めるにはOperation



、その完了を正確に記録するために特別なことをする必要があります。KVOAsyncOperation



を使用してASYNCHRON操作を手動で管理する必要があります。5.比類のない操作



Operation



それらを一連の操作に組み合わせて、複雑な依存関係グラフ(dependencies



を作成できるということです。これは、Operation



1つ以上の他の操作Operation



が完了するまで開始できない操作非常に簡単に識別できることを意味します。この記事では、プロトコルprotocol



使用しOperation



て、依存関係グラフ(dependencies



)の操作間でデータを転送する方法を示します。ただしdeadLock



、特に異なる操作の操作間に依存関係がある場合は、解決できないデッドロックを引き起こす可能性のあるサイクルを回避するために、依存関係グラフを調査する必要がありますOperationQueue







6.操作Operation



操作キューに転送したらOperationQueue



、ターンOperationQueue



自体が実行のための操作を開始するためのスケジュールを作成し、その実行を制御するため、この操作の制御を失います。ただし、メソッドcancel ()



使用して、操作が開始されないようにすることができます。この記事ではisCancelled



、操作の構築時にプロパティを考慮するOperation



方法と、操作キューのすべての操作を完全に削除する方法を示しますOperationQueue







7.結論は、インターネットから取得した画像でテーブルをスクロールする必要があり、「フィルタリング」や「エージング」などの追加アクションが必要な場合の実際のシナリオを反映するアプリケーションの開発を示しています。これにはかなりの時間がかかり、テーブルをスムーズにスクロールするには、画像を使用したすべての操作を外部で非同期に実行する必要がありますmain queue



。このアプリケーションでは、操作Operation



、特に非同期操作AsyncOperation



とその依存関係(dependencies



)を操作するための幅広い技術を使用しました。これにより、大幅な改善を達成することができましたUI







一緒にこの記事で、以前のギブあなたの中にマルチスレッド処理の全体像Swift 3



4



今存在しているiOSの上、。安定性(に加えて、マルチスレッドがSwift



バージョンのSwift 5



優先順位ABI



として宣言されている場合、現在敷設されているマルチスレッド処理の将来の可能性の議論に全面的に参加できますconcurrency



。バージョンSwift 5



完全に新しいマルチスレッドモデルの作業の開始のみが含まれ、その実装は将来のバージョンでも継続されます。Swift 5には、将来のマルチスレッドモデルの提案が既にありますだから「モーターをつけろ!」そして行ってください。



Swiftの進化はここで見ることができます



参照資料



WWDC2015。高度なNSOperations(セッション226)。

iOSのNSOperationsをお楽しみください

SwiftのNSOperationとNSOperationQueueのチュートリアル

GCDとOperationsのiOS同時実行性

IOSの 並行性

Swiftの同時実行性:1つの可能なアプローチ



All Articles