ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Swiftにおける構造化並行処理
他のコードと同時に実行する必要のあるコードがある場合、その作業に適したツールを選択することが重要です。ここでは、Swiftで作成できるさまざまな種類の同時実行タスクについて説明し、タスクのグループを作成する方法を示し、進行中のタスクをキャンセルする方法を確認します。また、構造化されていないタスクを使用したい場合のガイダンスも提供します。 このセッションを最大限活かしていただくためには、「Swiftのasync/awaitについて」を先にご確認いただくことをお勧めします。
リソース
関連ビデオ
WWDC23
WWDC22
WWDC21
-
ダウンロード
♪♪ ケイボンです 後に同僚のジョーが 加わります Swift5.5では 構造化同時並行処理により 並行プログラム作成の 新方法が導入されます 構造化同時並行処理を 支えるのは 構造化プログラミングです これは非常に直感的で 意識はされませんが 構造化された並行性を 理解するのに役立ちます 詳しく 見ていきましょう
初期のコンピューターでは プログラムは 一連の命令で書かれ 制御フローの ジャンプも許され 読みづらかった 今日では 言語は 制御フローの均一化のため 構造化プログラミングが 使われます たとえば if-then文では 構造化制御フローを 使用します ネストしたコードブロックは 上からへの移動のみが 条件付きで実行が 記述されます Swiftでは 囲まれた ブロックで定義されていれば そのブロックも 静的スコープを重視し 名前が表示されるだけです これは ブロックで定義された 変数の存続期間も 意味し ブロックを離れると 終了します 静的スコープを使った 構造化プログラミングでは 制御フローと変数存続期間を 分かりやすくします
一般的に 構造化された制御フローを 自然に順序付け 入れ子にできます これで プログラム全体を 上から下へ読めます これが構造化プログラミング の基本です ご想像どおり 今日の私たちにとって 非常に直感的で 簡単に当然と考えます しかし 今日のプログラムは 非同期 並行コードが特徴で コードを書きやすくするため 構造化プログラミングを 使用できませんでした 構造化プログラミングによる 非同期コードの 単純化を見てみましょう インターネットから 大量の画像を取得し サイズを順番にサムネイルに 変更する必要があるとします このコードは 画像を識別する 文字列群を取り込み 非同期で機能します この関数を呼び出しても 値が返されません これは 関数が その結果またはエラーを 指定の完了ハンドラーに 渡すためです このパターンにより 発信者は 後で応答を 受け取れます このパターンの結果 関数はエラー処理の 構造化制御フローを 使用できません これは関数からのエラーを 出すことに意味があり 入れるものではないからです このパターンは ループにより 各サムネイルを処理を することも防ぎます 関数完了後の実行コードが ハンドラの中で ネストされなくてはならず 再帰が必要です では 先ほどのコードを 構造化プログラミングによる 新しいasync/await構文に 書き換えて見てみましょう 関数から完了ハンドラー引数 を削除しました 代わりにタイプシグニチャで ”async” ” throws”という 注釈が付けられました また何も返さないのではなく 値を返します 関数の本体では 「await」を使用し 非同期アクションが発生し その後実行されるコードでは ネストは不要であることを 示しています これは サムネイルをループし 順番に処理できることを 意味します エラーのやりとりができ コンパイラは 私が忘れていないことを 確認します async/awaitの 詳細は 「Swiftのasync/awaitについて」 を参照ください
このコードは すばらしいのですが 何千の画像サムネイル作成 の場合はどうでしょう 各サムネイルの1回1つずつ 処理では理想的ではない さらに 各サムネイルの サイズが固定ではなく 別URLからダウンロードが 必要の場合は? 現在 同時並行性を 追加する機会があるため 複数のダウンロードを 並行実行できます プログラムに同時並行性の 追加タスクを作成できます タスクは Swiftの新機能です これは非同期関数と 連携して機能します タスクは 非同期コードを実行する 新しい実行コンテキストを 提供します 各タスクは 他の実行コンテキストに対し 同時に実行されます 安全で効率的な場合は 並行しての実行に自動的に スケジュールされます タスクはSwiftに 深く統合され コンパイラーは複数並行性の バグを防ぐのに役立ちます 非同期関数を呼び出した際も 呼び出しの新タスクは作成 されないことに注意します タスクを明示的に作成します 構造化された同時並行性は 柔軟性と簡潔性間のバランス に関するものであるため Swiftには複数の異なる 種類のタスクがあります このセッションの 残りの部分では ジョーと私でそれぞれの 種類のタスクを紹介し それらのトレードオフの 理解にむけて議論します 新しい構文形式で作成された async-letバインディング と呼ばれる 最も単純なタスクから 始めましょう 新しい構文形式の理解のため まず 通常の let bindingの 評価を分析したいと思います 2つの部分があります: equalsの右側にある イニシャライザ式と 左側にある変数の名前です レットの前後に他の ステートメントの 可能性があり ここにもそれらを含めます Swiftが let bindingに 達すると イニシャライザーが評価され 値が生成されます この例では URLからのデータ ダウンロードを意味し しばらく時間が かかる場合があります データが ダウンロードされた後 Swiftは 次のステートメントに 進む前にその値を変数名に バインドします 各ステップの矢印で 示されているように ここでは実行フローが1つ だけであることに注意します ダウンロードには時間が かかることがあるため プログラムでダウンロードを 開始し データが実際に要るまで 他の作業を続けてほしい この実現には既存の let binding前に asyncという単語を 追加するだけです これはasync-letと呼ばれ それを並行バインディングに 変えます 並行バインディングの評価は 順次バインディングとは かなり異なるので どのような機能かを学びます バインディングに遭遇する 直前から始めます 並行バインディング 評価には Swiftは最初に それを 作成したタスクの サブタスクである 新しい子タスクを作成します すべてのタスクは プログラムの 実行コンテキストを 表しているので このステップから2つの 矢印が同時に出てきます この最初の矢印は 子タスク用であり すぐデータのダウンロードを 開始します 2番目の矢印は親タスク用で 変数の結果をすぐに プレースホルダー値に バインドします この親タスクは 前のステートメントを 実行していたものと同じです 子がデータを同時に ダウンロードしている間 親タスクは引き続き ステートメントを実行し 並行バインディングに 進みます しかし 結果の実際の値を 必要とする式に到達すると 親は子タスクの 完了を待ち 結果のプレースホルダー が実行されます
この例では URLSessionの 呼び出しも エラーとなる可能性が あります これは結果を待つ事でエラー 発生があることを意味します その考慮のため「try」を 書く必要があります そして心配はいりません 結果の値を再度読み取っても その値は再計算されません async-letの機能を 見てきましたが これで サムネイルフェッチコードに 同時並行性を追加できます 単一の画像を独自の関数に 取得する前のコードの 一部を因数分解しました ここでの新機能はデータの ダウンロードも行っています 2つの異なるURLから: 1つはフルサイズ画像自体用 もう1つはメタデータ用で 最適なサムネイルサイズが 含まれています 順次バインディングでは letの右側に 「try await」と 書くことに注意してください エラーや一時停止が 発生する場所だからです 両方のダウンロードを 同時に実行するには これらの両方のletの前に 「async」と記述します ダウンロードは子タスクで 行われるようになったため 同時バインディングの右側に 「try await」と 書く必要はなくなりました この効果は 並行バインドされている 変数を使用する場合にのみ 親タスクが監視します 式がメタデータと画像データ を読み取る前に 「try await」と記述します また 並行バインドされた 変数を使用する場合 メソッド呼び出しや他の変更 は必要ありません この変数は順次 バインディングで 行ったのと同じタイプです
さて 私が話している これらの子タスクは 実際にはタスクツリーと 呼ばれる階層の一部です このツリーは 単なる実装の 詳細ではありません これは構造化同時並行処理 の重要な部分です キャンセル 優先度 タスクローカル変数 などのような タスクの属性に 影響を与えます 非同期関数から別の非同期 関数の呼び出しを行うときは 常に 同じタスクが呼び出し の実行に使用されます 関数fetchOneThumbnailは そのタスクのすべての 属性を継承します async-letのように新しい 構造化タスクを 作成する場合 現在の 関数が実行されている タスクの子になります タスクは特定の機能の 子ではありませんが その存続期間は機能に 限定される場合があります ツリーは 各親タスクと その子タスク間の リンクで構成されています リンクはすべての子タスクが 終了した場合 親タスクがその作業を 終了することしかできない というルールを適用します この規則は異常な制御フロー に直面しても当てはまり これにより子タスクが待機と なるのを防ぐことができます たとえば このコードでは 画像データタスクの前に 最初にメタデータタスクを 待ちます 最初に待機したタスクが エラーを出して終了した場合 fetchOneThumbnail関数は そのエラーを出して すぐ終了する必要があります でも2回目のダウンロードを 実行するタスクは どうなるでしょう? 異常終了時 Swiftは 待機していないタスクを キャンセル済みとして 自動的にマークし 関数の終了前にタスクが 完了するのを待ちます キャンセル済みマークでは タスクは停止しません 結果が不要になったことを タスクに通知するだけです 実際 あるタスクが キャンセルされると そのタスクの子孫である すべてのサブタスクも 自動的にキャンセルされます つまり URLSessionの導入が イメージを ダウンロードするの独自の 構造化タスクを作成した場合 そのタスクにはキャンセルの マークが付けられます 関数fetchOneThumbnailは 直接的または間接的に 作成した全構造化タスクが 完了すると エラーを出し最終的に 終了します これは構造化同時並行処理の 基本として保証されます ARCがメモリの存続期間を 自動的に 管理するように タスクの存続期間を 管理することで 誤ってタスクを リークすることを防ぎます これまで キャンセルが どのように 伝播するかについて 概要を説明してきました しかし タスクはいつ 最終的に停止するでしょう? タスクが重要な トランザクションの途中や ネットワーク接続が 開いている場合は タスクを停止するだけでは 正しくありません そのため Swiftでのタスクの キャンセルが協調します コードはキャンセルを 明示的にチェックし 適切な方法で実行を 終了する必要があります 現在のタスクの キャンセルステータスは 非同期であるかに関係なく 任意の関数から確認できます 特に 長時間実行される 計算が含まれる場合 これは キャンセルを 念頭に置いて APIを実装する必要が あることを意味します ユーザーは キャンセル 可能なタスクから コードを呼び出すことができ できるだけ速やかに計算が 停止することを期待します
協調キャンセルの使用が いかに簡単かを確認するため サムネイル取得の例に 戻りましょう
ここですべてのサムネイルを 取得するため 与えられた起点の関数を fetchOneThumbnail関数を 使用するように直しました キャンセルされたタスク内で この関数が呼び出された時 役に立たないサムネイルを 作成することによって Appを 保留したくありません したがって 各ループ反復の開始時に checkCancellationの 呼び出しを追加するだけです この呼び出しは 現在のタスクが キャンセルされた場合にのみ エラーを出します コードがより適している場合 現在のタスクの キャンセルステータスを ブール値として取得できます このバージョンの関数では 部分的な結果 要求されたサムネイルの一部 のみを含む辞書を 返していることに 注目してください これには 部分的な結果が 返る可能性があることを APIが明確に示すことを 確認する必要があります そうしないと タスクの キャンセルによりユーザーに 致命的なエラーが発生する 可能性があります そのコードは キャンセル中でも 完全な結果を必要と するからです ここまでasync-letが 構造化プログラミングの 本質を捉えながら プログラムに同時並行性を 追加する軽量構文を提供する ことを見てきました 次にお伝えしたいのは グループタスクです これは構造化同時並行処理の 優れた特性を一つも 失うことなくasync-let よりも柔軟性があります 前述のようにasync-letは 一定量の同時実行が 利用可能な場合 うまく機能します 前に説明した両方の機能 について考えてみましょう ループ内の サムネイルIDごとに fetchOneThumbnailを 呼び出して処理します これにより 正確に2つの 子タスクが作成されます その関数の本体をこの ループにインライン化しても 同時実行性の量は 変更されません 変数バインディングのように Async-letが見えます つまり次のループの反復が 始まる前に 2つの子タスクを完了する 必要があります しかし このループでタスクを 開始し 全サムネイルを 同時に取得したい場合は どうでしょうか? その場合 同時並行性の量は 配列内のIDの数に 依存するため 静的にはわかりません この状況に適したツールは タスクグループです タスクグループは動的な量の 同時実行性を提供するため 設計された構造化 同時並行処理の形式です withThrowingTaskGroup 関数を呼び出すことで タスクグループを 導入できます この関数はエラーを出せる 子タスクを作成するための スコープ付きグループ オブジェクトを提供します グループに追加されたタスク は定義されている ブロックのスコープより長く 存続することはできません forループ全体を ブロック内に配置したので グループを使用して動的な 数のタスクを作成できます asyncメソッドを 呼び出すことにより グループ内に子タスクを 作成します グループに追加されると 子タスクはすぐに そして任意の順序で 実行を開始します グループオブジェクトが スコープ外になると その中の全タスクの完了を 暗黙的に待機します グループタスクも構造化 されているため 前述のタスクツリールールの 結果です この時点で必要な同時 実行性は達成されています fetchOneThumbnailの 呼び出しごとに1つのタスク それがasync-letでさらに 2つのタスクを作成します これは構造化同時並行処理の もう1つの優れた特性です グループタスク内での async-letを使用や async-letタスク内に タスクグループを作成でき それでツリー内の同時並行性 レベルは自然に構成されます 現在このコードは実行する 準備が整っていません それを実行しようとすると コンパイラは データ競合の問題を 警告してくれます 問題は 各子タスクから 1つの辞書に サムネイルを挿入しようと していることです プログラムの同時並行性の 量を増やすときある誤りです データ競合が誤って 作成されます この辞書は2つの子タスクが サムネイルを同時に 挿入した場合 一度に複数の アクセスを処理できず クラッシュやデータ破壊を 引き起こす可能性があります 以前はこれらのバグを自分で 調査する必要がありましたが Swiftは静的チェックを 提供します そもそもこれらのバグが発生 するのを防ぐためです 新しいタスクの作成時は常に タスクが実行する作業は @Sendableクロージャと言う 新クロージャタイプ内です @Sendableクロージャの 本体は字句コンテキストで 可変変数を捕らえることを 制限されています これらの変数は タスクの起動後に 変更される可能性が あるためです これはタスクで捕らえた値を 安全に共有できる必要が あることを意味します たとえば IntやStringなどの 値型であるため またはactorsやclassesの ような複数のスレッドから アクセスするように設計された オブジェクトで 独自の同期を実装します 「Swiftアクターによる ミュータブルステートの保護」という このトピック専用の セッションがあります ぜひチェックして みてください 例のデータ競合を 回避するために 各子タスクに値を返すように することができます この設計により 親タスクは 結果を処理する 唯一の責任を負います この場合 各子タスクが 文字列IDを含む タプルとサムネイルの UIImage を返す必要があると 明記しました 次に 各子タスク内で 辞書に直接 書き込むのではなく 親が処理するキー値タプルを 返してもらいます 親タスクは新しいfor-await ループを使用して 各子タスクから結果を 反復処理できます for-awaitループは 子タスクから 完了順に結果を取得します このループは順番に 実行されるため 親タスクは安全に辞書への 各キーと値のペアを 追加できます これは値の非同期シーケンス にアクセスするため for-awaitループを使用する 一例にすぎません AsyncSequenceプロトコル 準拠の独自タイプの場合 for-awaitを使用してその 反復処理することもできます 「Swiftのasync/awaitについて」の セッションに詳細があります タスクグループは構造化 並行処理の形式ですが グループタスクとasync-let タスクの場合 タスクツリールール実装法に わずかな違いがあります このグループの結果を 反復処理するときに エラーで完了した子タスクが 発生したと仮定します そのエラーはグループの ブロックから出されるため そのときグループ内の全タスクは 暗黙的にキャンセル または 待機させられれます これはasync-letと同様に 機能します 違いはグループがブロック からの通常の終了により スコープ外に出たときに 発生します その場合 キャンセルは 暗黙的ではありません この動作でタスクグループを 使用して フォーク結合パターンを表現 することが容易になります ジョブは待機されるだけで キャンセルされないためです グループのcancelAllメソッド を使用しブロックの終了前に 全タスクを手動でキャンセル することもできます どのようにタスクを キャンセルしてもそれは 自動的にツリーに反映される ことに注意してください Async-letとグループタスク は Swiftで スコープ構造のタスクを提供 する2種類のタスクです ここで 構造化されていない タスクについて 説明をジョーに引き継ぎます ありがとう ケイボン こんにちは ジョーです ケイボンは タスクの階層が 明確なプログラムに 同時並行性を追加する場合 構造化同時並行処理が エラーの伝播 キャンセル およびその他の記帳を どのように簡素化するかを 示しました ただし プログラムにタスクを 追加するときに 必ずしも階層が あるとは限りません Swiftは構造化されていない タスクAPIも提供しますが これにより より多くの手動 管理が必要になるという 手間はありますが柔軟性が 大幅に向上します タスクが明確な階層に分類 されない可能性がある 状況は多くみられます 最も明らかなことですが 非-非同期コードから 非同期計算を実行するタスク を起動しようとしている場合 親タスクがまったくない 可能性があります 一方 タスクに必要な 存続期間が 単一のスコープまたは 単一の関数ですら その範囲に適合しない場合 があります オブジェクトをアクティブ化 するメソッド呼び出しに 応答してタスクを開始したい 場合があるとします 次に オブジェクトを 非アクティブ化する 別メソッド呼び出しに応答し その実行をキャンセルします
これはAppKitとUIKitで デリゲートオブジェクトを 実装するときに よく発生します UI作業はメインスレッドで 行う必要があり Swiftアクターセッションで 説明されているように Swiftは メインアクターに属する UIクラスを宣言することで これを保証します
コレクションビューがあり そのコレクションビューの データソースAPIをまだ使用 できないと仮定します 代わりに 先ほど作成した fetchThumbnails関数を 使用し コレクションビュー のアイテムが表示されたまま ネットワークからサムネイル を取得します ただ デリゲートメソッドは 非同期ではないため 非同期関数の呼び出しを 待つことはできません そのためのデリゲート アクションに応じて タスクを開始する必要があり そのタスクは 実際には開始した作業の 延長です この新しいタスクは UI優先度を使用して メインアクターで引き続き 実行する必要があります タスクの存続期間をこの 単一のデリゲートメソッドの スコープに制限 したくないだけです このような状況では Swiftの使用で構造化されて いないタスク作成できます コードの非同期部分を クロージャに移動し そのクロージャを渡し非同期 タスクを作成しましょう これで実行時に 何が起こるかです タスクを作成する段階に 達すると Swiftは起点スコープと 同アクター(メイン) で実行するように スケジュールします その間 制御はすぐに 呼び出し元に戻ります サムネイルタスクは開口部が あれば デリゲートメソッドの メインスレッドを 即ブロックすることなく メインスレッド実行されます この方法でタスク構築すると 構造化コードと 非構造化コードの中間点が 得られます 直接構築されたタスクは 起動されたコンテキストが ある場合 アクターを 引き続き継承します また グループタスクや async-letのように 起点タスクの優先度や その他の特性も継承します ただし 新しいタスクは スコープされていません その存続期間は起動場所の スコープに拘束されません 起点は非同期である 必要はありません スコープのないタスクは どこでも作成できます この柔軟性のすべてと 引き換えに 手動で管理する必要も あります その構造化同時並行処理では 自動的に処理されます キャンセルとエラーは 自動的に伝わらず タスクの結果は 明示的な 行動を取らない限り 暗黙的に待機されることは ありません
そこで コレクションビュー アイテムが表示された時に サムネイルを取得する タスクを開始しました サムネイルの準備完了前に アイテムがスクロールして 非表示となった時はタスクの キャンセルが必要です スコープのないタスクを 処理しているため そのキャンセルは 自動的には行われません それを実装しましょう タスクを作成後 取得した 値を保存しましょう タスクを作成時 この値を 行インデックスをキーとする 辞書に入れることができます 後でそれを使用し そのタスクをキャンセル できるようにします また タスクが終了したら 辞書から削除して タスクがすでに終了済みなら キャンセル不要とします ここで その非同期タスクの 内外で コンパイラがフラグを立てた データ競合をすることなく 同じ辞書にアクセスできる ことに注意してください デリゲートクラスは メインアクターに バインドされ新しいタスクは それを継承します そのためそれらが同時並行し 実行されることはありません メインのアクターバインドの クラス保存プロパティに データ競合を気にせず安全に タスク内でアクセスできます 一方 同じテーブル行の 表示削除がデリゲートに 後に通知された場合は タスクキャンセル値で cancelメソッドを 呼び出すことができます これでスコープに依存せず 実行される非構造化タスクを そのタスクの元コンテキスト から特性を継承しながら 作成する方法を確認しました ただし 元の コンテキストから 何も継承したくない場合が あります 最大限の柔軟性を得るため Swiftは分離されたタスクを 提供します 名前が示すように 分離されたタスクは コンテキストから 独立しています それらはまだ構造化 されていないタスクです それらの存続期間は起点の スコープに拘束されません ただし 分離された タスクは 起点スコープから 他に何も取得しません デフォルトではそれらは同じ アクターに制限されておらず 彼らが立ち上げられたのと 同じ優先度で 実行する必要はありません 分離されたタスクは 優先度などの一般的な デフォルト値で独立して 実行されます またオプションパラメーター を使用して起動して 新しいタスクの実行方法と 場所の制御も可能です
サーバーからサムネイルを 取得後 それらを ローカルディスクキャッシュ に書き込みたいとしましょう 後にその取得のためネット への再アクセスはありません メインアクターでキャッシュを 実行する必要はなく すべてのサムネイルの取得を キャンセルしても 取得したサムネイルを キャッシュするのは便利です では分離タスクを使用し キャッシュを開始しましょう タスクを分離すると新しい タスクの実行方法を より柔軟に設定 できるようになります キャッシュは メインUIに 干渉しない より低い優先度で行う 必要があります この新しいタスクを 分離するときに バックグラウンドの優先度を 指定できます
今からすこし 計画を立てましょう サムネイルに対し実行したい バックグラウンドタスクが 複数ある場合 これから どうすればいいでしょう? より多くのバックグラウンド タスクを分離できますが 構造化同時並行処理を 分離タスクの 内部で利用することも できます さまざまな種類のタスクを すべて組み合わせて それぞれの長所を 活用できます
バックグラウンドジョブ毎に 独立したタスクを 切り離さずタスクグループを 設定しグループの子タスクの 各バックグラウンドジョブ を生成できます そうすることには多くの 利点があります 後にバックグラウンドタスク をキャンゼルしたい時は タスクグループの使用して その最上位の分離タスクを キャンセルすれば全子タスク をキャンセルできます そのキャンセルは子タスク に自動的に伝わり ハンドル配列を追跡する 必要はありません さらに 子タスクは自動的に 親の優先度を継承します 全作業をバックグラウンド で維持するには分離タスクを バックグラウンドで実行する だけで済みます これは全子タスクに 自動的に伝わるため 忘れることを心配する必要も バックグラウンドの 優先順位を推移的に設定して 誤ってUI作業を不足させる 必要もありません これまででSwiftにある すべての 主要な形式のタスクを 見てきました
Async-letは固定数の 子タスクを許可し バインディングが スコープ外になった場合 キャンセルとエラー伝播の 自動管理により 変数バインディングとして 生成されます スコープにバインド されたままの 動的な数の子タスクが 必要な場合は タスクグループに 移動できます
スコープが適切ではないが 起点タスクに 関連している作業を中断 する必要がある場合は 構造化されていないタスクを 作成することはできますが それらを手動で管理する 必要があります また 最大限の柔軟性を 実現するために 手動で管理される 分離タスクも用意しています その起点からは何も 継承していません タスクと構造化同時並行処理 はSwiftがサポートする 一連の同時実行性機能の 一部にすぎません 他のすべての素晴らしい 講義をチェックして 他の言語とどう適合するかを 確認してください 「Swiftのasync/awaitについて」では 並行コードを書くための 非同期関数の詳細を提供し 構造化された基盤を 提供します アクターはデータ分離を 提供して データの競合から安全な 並行システムを作成します 「Swiftアクターによる ミュータブルステートの保護」 セッションで方法の 詳細を参照ください
タスクグループでのループ 「for await」は これはAsyncSequenceの 一例にすぎません これはデータの非同期 ストリームを操作する 標準インターフェースを 提供します 「Swiftのasync/awaitについて」 セッションでは シーケンスの操作に使用可能 なAPIを詳しく説明します
タスクはコアOSと統合され 低いオーバーヘッドと 高いスケーラビリティを 実現します 「Swiftの並行処理: 舞台裏」 セッションでは それがどう達成されるかの より技術的詳細を提供します
これらすべての機能が 組み合わされて Swiftでの並行コードの 記述が簡単で安全になります Appの興味深い部分に 焦点を合わせながら デバイスを最大限に 活用するコードを記述でき 並行タスク管理のメカニズム とマルチスレッドによって 発生する潜在的なバグの 心配はあまり考慮しません ご視聴ありがとう 残りの講義も楽しんで いただければ幸いです [明るい音楽]
-
-
1:57 - Asynchronous code with completion handlers is unstructured
func fetchThumbnails( for ids: [String], completion handler: @escaping ([String: UIImage]?, Error?) -> Void ) { guard let id = ids.first else { return handler([:], nil) } let request = thumbnailURLRequest(for: id) let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in guard let response = response, let data = data else { return handler(nil, error) } // ... check response ... UIImage(data: data)?.prepareThumbnail(of: thumbSize) { image in guard let image = image else { return handler(nil, ThumbnailFailedError()) } fetchThumbnails(for: Array(ids.dropFirst())) { thumbnails, error in // ... add image to thumbnails ... } } } dataTask.resume() }
-
2:56 - Asynchronous code with async/await is structured
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] for id in ids { let request = thumbnailURLRequest(for: id) let (data, response) = try await URLSession.shared.data(for: request) try validateResponse(response) guard let image = await UIImage(data: data)?.byPreparingThumbnail(ofSize: thumbSize) else { throw ThumbnailFailedError() } thumbnails[id] = image } return thumbnails }
-
7:59 - Structured concurrency with async-let
func fetchOneThumbnail(withID id: String) async throws -> UIImage { let imageReq = imageRequest(for: id), metadataReq = metadataRequest(for: id) async let (data, _) = URLSession.shared.data(for: imageReq) async let (metadata, _) = URLSession.shared.data(for: metadataReq) guard let size = parseSize(from: try await metadata), let image = try await UIImage(data: data)?.byPreparingThumbnail(ofSize: size) else { throw ThumbnailFailedError() } return image }
-
11:46 - Checking for cancellation by calling a method that throws
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] for id in ids { try Task.checkCancellation() thumbnails[id] = try await fetchOneThumbnail(withID: id) } return thumbnails }
-
12:16 - Obtaining the cancellation status of the current task
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] for id in ids { if Task.isCancelled { break } thumbnails[id] = try await fetchOneThumbnail(withID: id) } return thumbnails }
-
13:13 - Async-let is for concurrency with static width
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] for id in ids { thumbnails[id] = try await fetchOneThumbnail(withID: id) } return thumbnails } func fetchOneThumbnail(withID id: String) async throws -> UIImage { // ... async let (data, _) = URLSession.shared.data(for: imageReq) async let (metadata, _) = URLSession.shared.data(for: metadataReq) // ... }
-
13:58 - A task group is for concurrency with dynamic width
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] try await withThrowingTaskGroup(of: Void.self) { group in for id in ids { group.async { // Error: Mutation of captured var 'thumbnails' in concurrently executing code thumbnails[id] = try await fetchOneThumbnail(withID: id) } } } return thumbnails }
-
16:32 - Accessing the results of tasks within a group
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] try await withThrowingTaskGroup(of: (String, UIImage).self) { group in for id in ids { group.async { return (id, try await fetchOneThumbnail(withID: id)) } } // Obtain results from the child tasks, sequentially, in order of completion. for try await (id, thumbnail) in group { thumbnails[id] = thumbnail } } return thumbnails }
-
20:39 - Creating an unstructured task
@MainActor class MyDelegate: UICollectionViewDelegate { func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) { let ids = getThumbnailIDs(for: item) Task { let thumbnails = await fetchThumbnails(for: ids) display(thumbnails, in: cell) } } }
-
22:11 - Cancelling unstructured tasks
@MainActor class MyDelegate: UICollectionViewDelegate { var thumbnailTasks: [IndexPath: Task<Void, Never>] = [:] func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) { let ids = getThumbnailIDs(for: item) thumbnailTasks[item] = Task { defer { thumbnailTasks[item] = nil } let thumbnails = await fetchThumbnails(for: ids) display(thumbnails, in: cell) } } func collectionView(_ view: UICollectionView, didEndDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) { thumbnailTasks[item]?.cancel() } }
-
24:09 - Detaching a task
@MainActor class MyDelegate: UICollectionViewDelegate { var thumbnailTasks: [IndexPath: Task<Void, Never>] = [:] func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) { let ids = getThumbnailIDs(for: item) thumbnailTasks[item] = Task { defer { thumbnailTasks[item] = nil } let thumbnails = await fetchThumbnails(for: ids) Task.detached(priority: .background) { writeToLocalCache(thumbnails) } display(thumbnails, in: cell) } } }
-
24:57 - Creating a task group inside a detached task
@MainActor class MyDelegate: UICollectionViewDelegate { var thumbnailTasks: [IndexPath: Task<Void, Never>] = [:] func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) { let ids = getThumbnailIDs(for: item) thumbnailTasks[item] = Task { defer { thumbnailTasks[item] = nil } let thumbnails = await fetchThumbnails(for: ids) Task.detached(priority: .background) { withTaskGroup(of: Void.self) { g in g.async { writeToLocalCache(thumbnails) } g.async { log(thumbnails) } g.async { ... } } } display(thumbnails, in: cell) } } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。