Live Photosの実装をしていた際、複数の非同期処理から同じデータを触ってまれに値がおかしくなる不具合に遭遇し、原因を調べるうちにactorという仕組みに行き着きました。
1. classを置き換えるだけの機能だと誤解していた
最初、classをactorに書き換えるだけで自動的に安全になる、単なる上位互換のようなものだと思い込んでいました。
actor RecordStore {
private var records: [String] = []
}
実際にはactorの中のプロパティやメソッドに外部からアクセスする際、原則としてawaitを付けて呼び出す必要があります。単純に書き換えただけではビルドエラーが大量に出て、呼び出し側のコードもあわせて見直す必要があると気づきました。
2. awaitの付け忘れで気づかないまま放置していた
一通り書き換えを終えたつもりだったのですが、一部の呼び出し箇所でawaitを付け忘れたままコンパイラの警告を読み飛ばしてしまい、しばらく気づきませんでした。
Compilerからの警告:
actor-isolated property を await なしで参照しています
Xcodeが警告として出してくれていたにもかかわらず、大量の警告に紛れて見落としていました。actorへの移行作業では、警告を1件ずつ潰していく地道な作業が欠かせないと実感しました。
3. UIの更新でメインスレッドの制約を忘れていた
actor内の処理結果を使って画面を更新するコードを書いたところ、まれにクラッシュしたり表示が崩れたりする不具合が発生しました。
actor自体はデータ競合を防いでくれますが、UIの更新はメインスレッドで行うという制約は別問題として残ります。@MainActorを付けたクラスやメソッドと、通常のactorを混同しないよう、役割を整理して書き分ける必要がありました。
4. 既存のシングルトンをそのままactor化して呼び出し順が崩れた
アプリ全体で使っていたシングルトンのクラスをそのままactorに置き換えたところ、想定していた処理の順番が崩れ、データの整合性がおかしくなる不具合が発生しました。
actorへのアクセスは非同期になるため、これまで同期的に順番通り実行されていた処理が、呼び出し方によっては順序が保証されなくなることがあります。処理の順序が重要な箇所では、Taskのまとめ方やawaitの位置を見直し、意図した順番で実行されることを個別に確認する必要がありました。
まとめ
- actorはclassの単純な置き換えではない。外部からのアクセスには基本的に
awaitが必要になる - コンパイラの警告を1件ずつ確認し、
awaitの付け忘れを見逃さない - actor自体はデータ競合を防ぐが、UIの更新はメインスレッドで行うという制約は別に守る必要がある
- 既存のシングルトンをactor化する際は、処理の順序が変わらないか個別に確認する
「データ競合を防ぐ魔法の仕組み」というより、非同期処理全体の設計を見直すきっかけになった実装でした。
次は、App Store Connect APIを使ってみて分かったことをまとめます。
本記事は生成AI(Claude)を活用して執筆・編集しています。内容は実体験に基づき、執筆者本人が確認・監修しています。


コメント