[Swift6] アクター分離境界 (Actor isolation boundary) を考慮したcallback
この記事では、同期関数 (no-async) のcallbackにおけるアクター分離境界(以降、アクター境界)の考え方について解説します。
関連記事
この記事は上記に続く3つ目の記事です。以前の記事の内容を前提にしている場合があります。
同期関数のcallbackとは
async関数以前は、非同期に実行する場合callbackで結果を受け取る必要がありました。
例えば、以下のようなコードです。このような関数はSwift6によってどのように変わるでしょうか。
異なるアクター上でcallbackが呼び出される場合
まずは、callbackが異なるアクター上で呼び出されるケースを考えます。
この場合、callbackはアクター境界を越えるので、Sendable
にする必要があります。
しかし、それだけでは足りません。多くの場合はcallbackで結果を受け取り、元のアクター上に持っていく必要があります。
これを考慮すると以下のコードになります。
呼び出し側が煩雑になりますが、このやり方は呼び出し側で、スレッド呼び出しを必要最小限に制御することができるというメリットがあります。
その為、繰り返し高い頻度で呼び出され、オーバヘッドを極力減らしたい場合に検討するのが良いでしょう。
同じアクター上でcallbackを呼び出す場合
では、どうやって呼び出し側の煩雑さを軽減するかと言うと、callbackの呼び出しを同じアクター上にすることで、これが実現できます。
1. 呼び出し側でActorを指定する
@isolated(any) Function TypesによってClosureをどのアクター上で実行するべきかを引き継げるようになりました。
これを用いると、以下のコードになります。
この方法だと、@isolated(any)
は@MainActor
と同様に暗黙のSendable
として扱うべきにも関わらず、Swift6.0時点では扱われていない為、nonisolated(unsafe)
を使う必要があります。
Closure isolation controlというプロポーザルは出ているので、これが実装されればnonisolated(unsafe)
を使わない書き方にできる可能性があります。この方法はやや時期尚早と言えます。
2. GlobalActorを用いる
GlobalActorを使用している場合は、これを使うことでより簡略化することもできます。
呼び出し側・実装側が共に同じGlobalActorの場合は、適切な選択肢と言えます。
3. asyncにする
お気づきのことと思いますが、「同じアクター上でcallbackを呼び出す」というのはasync関数でやっていることと同じです。その為、async関数に変更するという選択肢もあります。
従来実装(non-Sendable
)の挙動
関連して、従来実装の場合はどのような挙動になるか解説します。
例えば、UIViewController.present(_:animated:completion:)などのcallbackは@Sendable
もアクターの指定もありません。
これは、何故動作しているのでしょうか?
動的アクター分離チェック
挙動が分かりやすい、UNUserNotificationCenter.requestAuthorization(options:completionHandler:)を例に取ります。
これはコンパイルは通りますが、アクター境界を越えてcallbackが実行される為、実行時にクラッシュします。
これは、Unmarked Sendable Closuresにあるように、動的アクター分離チェックが動作している為であり、以下のように変更することで正しく動作するようになります。
non-Sendable
で良い場合
ではUIViewController.present(_:animated:completion:)が何故問題ないのかと言うと、関数呼び出しとcompletionは必ずMainActor
で実行される為、アクター境界を越えることがない為です。
言い換えると、アクター境界を跨がない場合は従来通りの定義で良いということでもあります。
しかし、多くの場合Swift6下ではコンパイルエラーになる為、何らかの回避方法が必要になります。
この記事のコードはこちらから確認することができます
記事が気に入ったらチップを送ることができます!
You can give me a cup of coffee :)
Kyash ID: soranoba
Amazon: Wish List
GitHub Sponsor: github.com/sponsors/soranoba
PayPal.Me: paypal.me/soranoba
(Updated: )