ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Swiftアクターによるミュータブルステートの保護
2つの別々のスレッドが同じミュータブルなステートに同時にアクセスすると、データレースが発生します。データレースは簡単に起きますが、デバッグが難しいことで知られています。 コード内のデータへのアクセスを同期化するのに役立つSwiftのアクターを使って、これらのデータレースをどのように止めることができるかを説明します。アクターの仕組みと、アクター間で値を共有する方法をご確認ください。アクターの分離がプロトコル準拠に与える影響について説明します。そして最後に、メインアクターを紹介します。これは、必要なときにコードが常にメインスレッド上で実行されることを保証するための新しい方法です。 このセッションを最大限活かしていただくためには、「Swiftのasync/awaitについて」を先にご確認いただくことをお勧めします。
リソース
- SE-0302: Sendable and @Sendable closures
- SE-0306: Actors
- SE-0313: Improved control over actor isolation
- SE-0316: Global actors
- The Swift Programming Language: Concurrency
関連ビデオ
WWDC22
WWDC21
-
ダウンロード
♪ (Swiftアクターによる ミュータブルステートの保護) こんにちは ダリオ・レクシンです AppleのSwiftチームの エンジニアです 今日はDougと一緒に Swiftのアクタや 並列のSwift Appケーション でアクタを使い 可変状態を保護する方法を を説明します 並列プログラムを 書く時に 根本的に難しい問題の1つに データ競合の回避があります データ競合は 2つの別のスレッドが 同じデータに並列で アクセスする時に発生し そのアクセスの最低1つは 書き込みです データ競合は自明で コンストラクトされますが デバッグは非常に大変です これはカウンターを インクリメントして 新しい値を返す演算つきの 単純なカウンタークラスです では早速2つの 並列タスクから インクリメントしてみましょう これは悪いアイデアです 実行のタイミングにより 1と2または2と1が 出てくるでしょう これは予想できたことで どちらのケースでも カウンターは不変状態に 残されることになります しかしデータ競合を 導入したので 両方のタスクで0が読まれ 1が書き出されると 1と1が得られます または両方のインクリメント 演算で return文が発生すれば 2と2です データ競合の回避とデバッグは 非常に困難です 競合を引き起こすデータアクセス はプログラムの様々な 場所にある可能性があるので 非局所的な推論が必要です またオペレーションシステム のスケジューラが プログラム実行のたびに 並列タスクをバラバラに インタリーブする可能性があるため 非決定性です データ競合は共有の 可変状態に引き起こされます データが変わらなかったり 複数の並列タスクで 共有されないのなら そこにデータ競合は 発生しません データ競合を防ぐ方法の1つに 値の意味論を使って 共有可変状態を排除すると いうものがあります 値型の変数では 可変は全てローカルです さらに値の意味論の型の 「let」プロパティ は非常に不変なので バラバラの並列タスクから 安全にアクセスできます Swiftは最初から 値の意味論を推奨しています プログラムの推論がしやすく また並列タスクで 使用しても 安全だからです この例ではいくつかの値の 配列を作成します 次に配列を2番目の変数に 割り当て 配列の各コピーに別の値を 加えます 最後の両方の配列を 出力すると 両方のコピーに 一緒に初期化された 値が含まれていますが 各付加価値は 付加された別々の コピーにのみ 表示されています Swiftの標準ライブラリの 型の大部分には 辞書や この例では配列など コレクション型を含む 値の意味論があります さて 値の意味論で 全てのデータ競合の 解決が確立したので 次は構造体にすることで カウンターを 値型にします また値プロパティを 修正できるように 増分関数をmutatingと する必要があります カウンターを修正 しようとすると カウンターがletなので コンパイラエラーになり 変異できなくなっています 変異できるようにするため カウンター変数を varに変えたいのですが カウンターが並列タスクの 両方に参照されるので 競合状態が 残ってしまいます 幸運なことに コンパイラが この非安全なコードのコンパイルを できないようにしてくれます その代わり カウンターを 各並列タスク内の ローカルな可変変数に割り当てます ここで例を実行すると 両方の並列タスクに 常に1が出力されます しかしコードはこれで 競合なしになりましたが この動作はもう 不要です 共有可変状態が 必要なケースが まだあると示し続ける ことになります 並列プログラムに 共有可変プログラムがあると 何らかの形の 同期によって 共有可変状態の 並列使用でデータ競合が 起こらないないように する必要があります 同期には アトミックやロックなど 低レベルのものから シリアルディスパッチキュー など高レベルなものまで 多くの基本命令があります これら基本命令のそれぞれには 独自の様々な強みがありますが 全く同じ大きな弱点が あります 厳格な原則に基づいて 毎回とても丁寧に 扱わないと データ競合が 起こるという点です そこでアクタの出番です アクタは共有可変状態 のための同期メカニズムです プログラムの残りの部分から 隔離される独自の 状態を持ちます その状態にアクセス するには アクタを経由するのが 唯一の方法です アクタを経由すれば アクタの同期メカニズム により 他のデータが アクタの状態に 並列アクセスしないように なります これでロックの 手動使用や シリアルディスパッチキュー と同じ 相互排除プロパティが得られますが アクタでは これがSwiftによって 根本的に保証されています 同期の実行を忘れることも ありません Swiftからコンパイラエラーは 生成されるからです アクタはSwiftの 新しい型の一種で Swiftの全ての型と 同じ 機能を提供します プロパティやメソッド 初期化子 添字 などの機能を持ち プロトコルに適合し 拡張で増強することが できます クラスと同じく 参照型です アクタの目的は共有可変状態 を式で表すことだからです 実際 アクタ型の主な特徴は インスタンスデータを プログラムの他の 部分から隔離し データへの同期アクセスを 可能にすることです こうした特別な動作は全て コアとなる考えに従っています ここではカウンターを アクタとして定義しました このカウンターへの インスタンス プロパティ値と 値を増分し 新しい値を返す increment()メソッドが まだあります その違いは アクタのおかげで 値が並列アクセス されないことです このケースでは increment()メソッドは 呼び出されると アクタ上で実行中の 他のデータがない状態で 完了に達します これにより アクタ状態で データ競合が発生する 可能性が確実に 排除されます データ競合の例に 戻ってみましょう 同じカウンターの 増分を 試みている2つの 並列タスクがあります アクタの内部同期 メカニズムは 一方の増分呼び出しの完了が もう一方を開始前に 行われるようにします それで1と2または2と1が 得られます 両方とも有効な並列実行 だからです しかしアクタの内部同期が アクタ状態の データ競合の可能性を 排除したため 同じ値を二度得たり 値のスキップが起こったり することはありません 並列タスクの両方が カウンターを同時に 増分しようとした時に 何が起こるか考えてみましょう 一方がまず到着し もう一方はターンを 待つことになります しかし2番目のタスクが アクタでのターンを 待つようにするには どうすればよいでしょうか? そのメカニズムが Swiftにあります 外からアクタを操作する場合 非同期で行うことに なります アクタがビジーだと 実行中のCPUで 他の有用な仕事が行われるよう コードが停止されます アクタがまたフリーに なると コードを起こして 実行を再開し 呼び出しがアクタで 実行されるようになります この例のawaitキーボードは アクタへの 非同期呼び出しで このような 停止が起こる可能性を 示唆しています 不要な遅いリセット演算を 追加して 反例をもう少し 拡大してみましょう この演算では値が 0に戻っていて その後呼び出しが 適切な回数を増分し 新しい値のカウンターが 得られます このresetSlowly()メソッドは カウンターアクタ型の 拡張で定義されているため アクタの中にあります つまりアクタ状態に 直接アクセスでき カウンター値を0に リセットします また増分呼び出しの中など アクタの他のメソッドも 同期で呼び出します アクタで実行中だと 既に分かっているので awaitの必要もありません これはアクタにとって 重要なプロパティです アクタの同期コードは 中断されることなく 常に完了に達します そのためアクタ状態の コンカレンシーの効果を 検討する必要なく 同期コードを連続で 推論することができます 非同期コードを 中断なく実行できることを 強調してきましたが アクタはお互いまたは システムの他のコードと 相互作用することがあります 非同期コードと アクタについて 少しお話しします でもまずは もっと良い例が必要です ここでは画像ダウンローダー アクタを構築しています 他のサービスから 画像をダウンロードするものです またダウンロードした画像を キャッシュに保存し 同じ画像が何度もダウンロード されないようにします ロジカルフローは とても単純で キャッシュをチェックして 画像をダウンロードし 戻る前に画像をキャッシュに 記録します これはアクタなので コードに低レベルの データ競合がなく 画像をいくらでも並列で ダウンロードできます アクタの同期メカニズム でキャッシュインスタンス プロパティにアクセスする コードが実行されるのは 一度に1つのタスクのみと 保証されるため キャッシュが破損する ことはありません とはいえ ここのawaitキーボードは 非常に重要なことを 伝えています awaitがいつ起こったかを 問わず await関数はこの時点で 停止されるということです CPUが解放され プログラムの その他のコードが実行可能となり 全体のプログラム状態が その影響を受けます 関数が再開されると 全体のプログラム状態が 替わります awaitの後に 待機しないかもしれない awaitに 先立って 状態について推測 しないようにしましょう 同じ画像を同時に フェッチ使用する 2種類の並列タスクが あるとします 最初のものはキャッシュ エントリがないと判断すると サーバーから画像の ダウンロードを開始し ダウンロードに時間がかかるので しばらく停止します 最初のタスクが画像を ダウンロード中 同じURLで新しい画像が サーバーにデプロイされると 2番目の並列タスクが そのURLから 画像をフェッチ しようとします 今回もキャッシュエントリが ないようです 1番目のダウンロードが まだ完了していなく 2番目のダウンロードを 開始したからです またダウンロード完了中に 停止されています しばらくして ダウンロードのいずれかで 恐らく1番目だとして そこで タスクがそのアクタでの 実行を再開します キャッシュを追加し 結果として猫の画像を 返します さて 2番目のタスクの ダウンロードも完了し 再起されます 取得した悲しい猫の 画像で キャッシュの同じエントリを 上書きしています キャッシュに画像が既に 追加されていても 同じURLから別の画像が 得られます これはびっくりです 期待していたのは 画像をキャッシュすると 同じURLから同じ 画像を取得し そのためユーザーインターフェイス が少なくとも キャッシュを手動で消去するまでは 一貫していることです でもここではキャッシュされた 画像が変わっています 低レベルのデータ競合は ありませんが await全体の状態で 推測をしたので バグの可能性に つながりました これを修正するには await後の推測を確認します 再開時にキャッシュにエントリが 既にあれば 元のバージョンを残し 新しいものは捨てます もっと良いのは 冗長なダウンロードを 完全に避けることでしょう そのソリューションを 関連動画で 説明しています アクタ再入可能性で デッドロックが防がれ 前進が保証されますが 各await全体で 推測の確認が 求められます 再入可能性に備えるためには 同期コード内でアクタ状態の 変異を行います 同期関数内で 行うのが理想です 全ての状態変更が きちんとカプセル化されるからです 状態変更を行うと アクタが一時的に 矛盾状態になります awaitの前に一貫性を 回復するようにしましょう またawaitが停止ポイントに なる可能性もあります コードが停止されると コードが再開される前に プログラムなどが 進行していきます 時計やタイマーなど グローバル状態に 行った推測は awaitの後に 確認する必要があります それではこれからDougが アクタ隔離について お話します ありがとう Dario アクタ隔離はアクタタイプの 動作としては根本的なものです Darioがアクタの外からの アクタ隔離について Swift言語モデルで アクタ隔離が どう保証されているか 説明しました このセクションでは アクタ隔離が プロトコル適合性や クロージャ クラスなど 他の言語機能とどう 相互作用するかに触れます アクタは他のタイプと同様 プロトコルの必要条件を 満たせる限り プロトコルに 適合することができます 例えば LibraryAccount アクタを Equatableプロトコルに 適合させてみましょう 静的な透過性メソッドは ID番号に基づいて 2つの図書館アカウントを 比較します メソッドが静的なので セルフインスタンスはなく アクタに隔離されていません その代わりアクタタイプに 2つのパラメーターがあり この静的メソッドは 2つの圏外にあります 実装がアクタの不可変状態に アクセスしているだけなので これは問題ありません 例をもっと掘り下げ 図書館のアカウントを Hashableプロトコルに 適合させてみましょう そのためにはhash(into)演算を このように 実装する必要があります しかしSwiftコンパイラから この適合は許可されていないと エラーが出ます 何があったのでしょうか Hashableにこういう風に 適合するというのは この関数がアクタ外から 呼び出されるということですが hash(into)は非同期では ありません そのためアクタの隔離を 維持できないのです これはメソッドを非隔離に することで直せます 非隔離とは このメソッドがアクタ外 にあるとして扱われても 構文的には アクタ上で 記述されるということです これはHashableプロトコルから 同期必要条件を 満たすことができると いう意味です 非隔離のメソッドは アクタ外にあるとして 扱われるので アクタ上で可変状態を 参照できません 不可変のID番号を 参照しているので このメソッドは問題ありません booksOnLoan配列など 別のもので値変換する場合 エラーになります 外からの可変状態への アクセスは データ競合を 許可するものだからです 以上 プロトコルの 適合性についてです 次はクロージャについてです クロージャはちょっとした 関数で 1つの関数内で 定義され 後になって呼び出すために 別の関数に 渡されるものです 関数と同様クロージャも アクタ隔離型または 非隔離です この例では 貸し出し中の本から いくつか読み込み 読んだページの総数を 返します reduceの呼び出しには 読み込みを行う クロージャが関係しています このreadSome呼び出しには awaitはありません それはこのクロージャが アクタ隔離型関数「read」 内で形成され それ自体アクタ隔離型だからです リダクション演算 が同期実行され 並列アクセスを 引き起こしかねない 他のスレッドにクロージャを 逃がすことはできないので これは安全だと 分かっています では少し趣向を 変えてみましょう 今は読む時間がないので 後で読むとします ここでデタッチされたタスクを 作ります デタッチされたタスクは クロージャを アクタの他の仕事と 並列で実行します 従ってクロージャが アクタに乗れないか データ競合が起こる ことになります だからこのクロージャは アクタに隔離されていません read()メソッドを 呼び出したい時には awaitに示される通り 非同期でしなければなりません コードがアクタの内外で実行 される場合それぞれの コードのアクタ隔離については ここまでです 次はアクタ隔離とデータ についてお話しします 図書館アカウントの例では 実際の本のタイプには あえて触れませんでした 構造体のような 値型だと 思っています これは良い選択です 図書館アカウント アクタのインスタンスの 全ての状態は 自己完結型だからです 早速このメソッドを 呼び出して 読む本を無作為に選ぶと 読める本を一部 得られます 本に行った変更が アクタに影響を与えることや その逆はありません しかし本をクラスにすると ちょっと違ったことになります 図書館アカウントアクタ は本のクラスのインスタンスを 参照します そこ自体では問題では ありません しかし無作為に本を 選ぶメソッドを 呼び出したら どうなるのでしょうか? ここには参照情報つまり アクタの可変情報があり これはアクタの外で 共有されています これだとデータ競合の 可能性があります では本のタイトルを 更新すると 修正が アクタから アクセス可能な 状態で起こります visit()メソッドは アクタ上にないので 修正がデータ競合に なってしまいます 値型とアクタは両方とも 安全に並列利用できますが クラスにはまだ問題が あります 問題なく並列利用できる タイプは Sendableです Sendableタイプでは その値を様々なアクタで 共有することができます 値をある場所から 別の場所にコピーし 両方の場所で 値のコピーを 互いに干渉せず 修正できる場合 それはSendableと言えます Darioが先程話した通り 各コピーが独立しているので 値型はSendableです アクタタイプも Sendableです 可変状態への アクセスを同期するからです クラスもSendableに なりえますが 注意深く実装された 場合のみです 例えば クラスやその 全てのサブクラスに 不可変データしかない場合は Sendableと言えます またはクラスの内部で 同期が行われる場合 例えば安全な並列アクセスを 可能にするロックでは Sendableになりえます しかしクラスのほとんどは このどちらでもなく Sendableになれません 関数は必ずしも Sendableではないので アクタ間で安全に パスできる 関数のために新しい 関数タイプがあります これについては 後でまた触れます アクタは いや 並列コードは全て Sendableタイプの観点から 主に通信すべきです Sendable型はデータ競合 からコードを守ります これはSwiftが 最終的に静的にチェックを 始めるプロパティです その点では アクタ境界に non-Sendable型を渡す エラーとなります 型がSendableだと どのように知るのでしょうか Sendableは プロトコルなので 型はSendableに 他のプロトコルと 同じ方法で適合すると 述べます するとSwiftが 型がSendableとして 成立しているか チェックします Nookの構造体は 保存されたプロパティが全て Sendable型であれば Sendableとなります 一方Authorは 実際にクラスで つまり著者の配列は Sendableではありません Swiftはコンパイラ エラーを生成し BookがSendableに なれないと示します 総称型では Sendableであるかどうかは 総称因数による 場合があります Conditional Conformance を使えば 適切な時にSendableを 伝搬することができます 例えばペアの型は 両方の総称引数がSendable の時だけSendableになります 同じアプローチが Sendableタイプの配列を Sendableそのものだと 結論付けるときにも使われます 並列で共有して問題ない 値型には Sendable適合性の導入を おすすめします そうした型を アクタ内で使うと Swiftがアクタ中で Sendableの実行を始め コードの準備が整います 関数そのものも Sendableになりえます つまり関数の値を アクタ中に渡しても 安全だということです これはクロージャにとっては 特に重要です クロージャがデータ競合を 防ぐのを制限するからです 例えば Sendableクロージャ は可変のローカル変数を キャプチャできません ローカル変数の データ競合を 引き起こすからです クロージャがキャプチャする ものは全て クロージャがnon-Sendable型を アクタ境界中に 移すのに使われないよう Sendableである必要が あります そして最後に 同期Sendableクロージャは アクタを外から 実行されるよう コードに許可するため アクタ隔離型にはなれません この動画では Sendableクロージャという 考えをよく使いました デタッチされたタスクを 作成する演算には ここで関数型に @Sendableで書かれた Sendable関数が使われます 最初のほうの反例を 覚えているでしょうか 値型のカウンターを 構築しようとしていましたね そして同時に2つの クロージャから 修正しようと していました これは可変ローカル変数では データ競合となります しかしデタッチされたタスクの クロージャはSendableなので Swiftはここでまだ エラーを出しています Sendableの関数型は 並列実行がどこで起こる 可能性があり 結果データ競合を防ぐために 用いられます これは先程見た もう1つの例です デタッチされたタスクの クロージャはSendableなので アクタに隔離してはいけないと 分かっています 従って相互作用は非同期で なくてはなりません Sendable型とクロージャにより 可変状態がアクタ中で 共有されないことをチェックし アクタの隔離が維持され 並列で修正されるように できます さてアクタの型や それがプロトコルや クロージャ Sendable型と 相互作用する方法を お話してきました もう1つ触れるべき アクタがあります メインアクタと呼んでいる 特別なものです Appを構築中 メインスレッドについて 考える必要があります コアなユーザーインターフェイス のレンダリングの発生や UIイベントの処理は ここで起こるからです UIに関連する演算は 通常メインスレッドから 行われる必要があります しかし全部の作業を メインスレッドでしたくは ないものです メインスレッド上で 作業をし過ぎると 入力・出力演算が 遅くなったり サーバーとの相互作用が ブロックされたりし UIがフリーズします そのためメインスレッドが UIに相互作用する時には メインスレッドでの作業に注意し 計算コストが高いか 待ち時間の高い演算 のためにメインスレッドを 速く抜ける必要があります さて可能な時に メインスレッドを抜けたら DispatchQueue.main.async をコードで呼び出し メインスレッドで 実行すべき 特定の演算がある場合です メカニズムの詳細から 戻ると このコードの構造には 若干見覚えがありますね 実際 メインスレッドとの 相互作用は アクタとの相互作用に 非常に似ています メインスレッドで既に実行中と 知っているなら UI状態に安全にアクセスし 更新できます メインスレッドで 実行中でなければ 非同期で相互作用する 必要があります これこそがアクタの仕組みです メインスレッドを記述する 特別なアクタを メインアクタと呼ぶのです メインアクタは メインスレッドを表す アクタなのです 通常のアクタとは 2つの点で大きく異なります まずメインアクタが メインディスパッチキュー経由で 同期の全てを行います つまりランタイムの 観点から言うと DispatchQueue.main を使うことで メインアクタは互換可能 になります 次にメインスレッド上に 置かれるべきコードとデータは Swift UIやAppleKit UIKitやその他システムワーク など色々なものに入っています ご自身のビューやビュー コントローラー データモデルのUI関連部分 で広く使えます Swiftの並行性により メインアクタの属性で それがメインアクタで実行 されなければならないと 宣言することができます ここで確認した演算は 完了しているので 常にメインスレッドで 実行されます メインアクタの外から 呼び出す場合 その呼び出しが メインスレッドで 非同期的に行われるまで 待機します メインスレッドで実行 しなければならないコードを メインアクタ―とすると DispatchQueue.main をいつ使うかについて 推論する必要は皆無です Swiftはこのコードが 常にメインスレッドで 実行されるようにします 型もメインアクタ上に 置かれ よってメンバー全てと そのサブクラスが メインアクタに置かれます これはUIと相互作用 しなければならない コードベース部分では 便利です ほとんど全てをメインスレッドで 実行しなければならないからです 個々のメソッドは 非隔離キーワード経由で 通常のアクタで 慣れているのと 同じルールでオプトアウト できます UI関連のタイプや演算に メインアクタを使い 他のプログラムの状態に 独自のアクタを 導入することで Appが安全で 並行性を 正しく使うよう 構築できます このセッションでは アクタが可変状態を 並列アクセスから守っている 方法を説明しました つまりアクタ隔離を使ったり 連続実行を行うために アクタの外から非同期アクセスを 要求したりしています ご自身のSwiftコードで アクタを使い 安全な並列抽出を 構築しましょう ご自分のアクタを 非同期アクタに実装するには 再入可能性を常に念頭に入れて デザインしましょう コードにawaitがあると 推論が無効になってしまいます 値型とアクタは 共同で データ競合を排除します 独自の同期を処理しない クラスや 共有可変状態を 実行する 他のnon-Sendable型には 注意してください 最後に コードでは UIと相互作用する メインアクタを使い メインスレッド上にあるべき コードが常にメインスレッドに あるようにしましょう ご自身のAppでの アクタの使い方について詳細は Swift並行性 のためのApp更新についての セッションをご覧ください またアクタなどSwift 並行性モデルの実装 については 「Behind the scenes」 セッションをご覧ください アクタはSwift並行性 モデルの主要な部分です async/awaitと動作し 正確で効果的な 並列プログラムを 構築しやすくする 構造化された並行性です 皆さんがどんなものを構築するか 楽しみにしています ♪
-
-
0:42 - Data races make concurrency hard
class Counter { var value = 0 func increment() -> Int { value = value + 1 return value } } let counter = Counter() Task.detached { print(counter.increment()) // data race } Task.detached { print(counter.increment()) // data race }
-
2:20 - Value semantics help eliminate data races
var array1 = [1, 2] var array2 = array1 array1.append(3) array2.append(4) print(array1) // [1, 2, 3] print(array2) // [1, 2, 4]
-
2:59 - Sometimes shared mutable state is required
struct Counter { var value = 0 mutating func increment() -> Int { value = value + 1 return value } } let counter = Counter() Task.detached { var counter = counter print(counter.increment()) // always prints 1 } Task.detached { var counter = counter print(counter.increment()) // always prints 1 }
-
5:23 - Actor isolation prevents unsynchronized access
actor Counter { var value = 0 func increment() -> Int { value = value + 1 return value } } let counter = Counter() Task.detached { print(await counter.increment()) } Task.detached { print(await counter.increment()) }
-
7:51 - Synchronous interation within an actor
extension Counter { func resetSlowly(to newValue: Int) { value = 0 for _ in 0..<newValue { increment() } assert(value == newValue) } }
-
9:02 - Check your assumptions after an await: The sad cat
actor ImageDownloader { private var cache: [URL: Image] = [:] func image(from url: URL) async throws -> Image? { if let cached = cache[url] { return cached } let image = try await downloadImage(from: url) // Potential bug: `cache` may have changed. cache[url] = image return image } }
-
11:50 - Check your assumptions after an await: One solution
actor ImageDownloader { private var cache: [URL: Image] = [:] func image(from url: URL) async throws -> Image? { if let cached = cache[url] { return cached } let image = try await downloadImage(from: url) // Replace the image only if it is still missing from the cache. cache[url] = cache[url, default: image] return cache[url] } }
-
11:59 - Check your assumptions after an await: A better solution
actor ImageDownloader { private enum CacheEntry { case inProgress(Task<Image, Error>) case ready(Image) } private var cache: [URL: CacheEntry] = [:] func image(from url: URL) async throws -> Image? { if let cached = cache[url] { switch cached { case .ready(let image): return image case .inProgress(let task): return try await task.value } } let task = Task { try await downloadImage(from: url) } cache[url] = .inProgress(task) do { let image = try await task.value cache[url] = .ready(image) return image } catch { cache[url] = nil throw error } } }
-
13:30 - Protocol conformance: Static declarations are outside the actor
actor LibraryAccount { let idNumber: Int var booksOnLoan: [Book] = [] } extension LibraryAccount: Equatable { static func ==(lhs: LibraryAccount, rhs: LibraryAccount) -> Bool { lhs.idNumber == rhs.idNumber } }
-
14:15 - Protocol conformance: Non-isolated declarations are outside the actor
actor LibraryAccount { let idNumber: Int var booksOnLoan: [Book] = [] } extension LibraryAccount: Hashable { nonisolated func hash(into hasher: inout Hasher) { hasher.combine(idNumber) } }
-
15:32 - Closures can be isolated to the actor
extension LibraryAccount { func readSome(_ book: Book) -> Int { ... } func read() -> Int { booksOnLoan.reduce(0) { book in readSome(book) } } }
-
16:29 - Closures executed in a detached task are not isolated to the actor
extension LibraryAccount { func readSome(_ book: Book) -> Int { ... } func read() -> Int { ... } func readLater() { Task.detached { await read() } } }
-
17:15 - Passing data into and out of actors: structs
actor LibraryAccount { let idNumber: Int var booksOnLoan: [Book] = [] func selectRandomBook() -> Book? { ... } } struct Book { var title: String var authors: [Author] } func visit(_ account: LibraryAccount) async { guard var book = await account.selectRandomBook() else { return } book.title = "\(book.title)!!!" // OK: modifying a local copy }
-
17:39 - Passing data into and out of actors: classes
actor LibraryAccount { let idNumber: Int var booksOnLoan: [Book] = [] func selectRandomBook() -> Book? { ... } } class Book { var title: String var authors: [Author] } func visit(_ account: LibraryAccount) async { guard var book = await account.selectRandomBook() else { return } book.title = "\(book.title)!!!" // Not OK: potential data race }
-
20:08 - Check Sendable by adding a conformance
struct Book: Sendable { var title: String var authors: [Author] }
-
20:43 - Propagate Sendable by adding a conditional conformance
struct Pair<T, U> { var first: T var second: U } extension Pair: Sendable where T: Sendable, U: Sendable { }
-
24:19 - Interacting with the main thread: Using a DispatchQueue
func checkedOut(_ booksOnLoan: [Book]) { booksView.checkedOutBooks = booksOnLoan } // Dispatching to the main queue is your responsibility. DispatchQueue.main.async { checkedOut(booksOnLoan) }
-
25:01 - Interacting with the main thread: The main actor
@MainActor func checkedOut(_ booksOnLoan: [Book]) { booksView.checkedOutBooks = booksOnLoan } // Swift ensures that this code is always run on the main thread. await checkedOut(booksOnLoan)
-
26:21 - Main actor types
@MainActor class MyViewController: UIViewController { func onPress(...) { ... } // implicitly @MainActor nonisolated func fetchLatestAndDisplay() async { ... } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。