概要
MIXIのminimo負債返済チームでインターンをしていたのですが、actorの使い方で迷ったので備忘録として残します。
Actor
actor
actorは一人しか入れない部屋と考えられる。
複数のタスクが同時にアクセスしてきても、順番に処理される仕組みを持っているため、データ競合を防ぐことができる。
普通のクラスだと危険
class Counter {
var value = 0
func increment() { value += 1 }
}
let counter = Counter()
Task { counter.increment() }
Task { counter.increment() }
同時に呼ばれるとvalueの値が壊れる可能性がある(値が飛ぶ)
actor を使うと安全
actor Counter {
private var value = 0
func increment() { value += 1 }
func current() -> Int { value }
}
let counter = Counter()
Task { await counter.increment() }
Task { await counter.increment() }
-
actorの中は必ず順番待ちになる - そのため同時アクセスしてもデータが壊れない
- メソッド呼び出し時に await が必要なのは「部屋に入れるまで待つ」ため
@MainActor
@MainActor は、Swift が用意している 特別なグローバル actor です。
普通の actor は自分で定義しますが、@MainActor は メインスレッドに直結した「一人しか入れない部屋」 があらかじめ用意されている。
- UI の処理は必ずメインスレッドで行う必要がある
-
@MainActorを付けることで、その処理は必ずメインスレッドで実行されることが保証される
@MainActor
func updateLabel(_ text: String) {
label.text = text // 必ずメインスレッドで実行
}
Task {
let name = await fetchUserName() // バックグラウンド
await updateLabel(name) // UI 更新だけメインで安全に実行
}
@MainActorの使い方
1. Task { @MainActor in … }
Task { @MainActor in
label.text = "Hello" // このタスク全体がメインスレッドで動く
}
- タスク全体を最初から最後までメインスレッドで実行する
- 「とにかく全部メインでやって!」というときに便利
- ただし 重い処理をここに書くと UI が固まる(フリーズの原因になる)
- 軽い UI 更新や状態変更だけに使うのがベスト
2. @MainActor func
@MainActor
func applyName(_ name: String) {
label.text = name
}
Task {
let name = await fetchUserName() // 裏で処理
await applyName(name) // ここだけメインにホップ
}
- 関数単位で「この処理は必ずメイン」と宣言できる
- バックグラウンド処理と UI 更新を分離できるので、コードが安全&読みやすい
- テストやリファクタリングでも有効
→@MainActorがついているだけで、呼び出し側も「これは UI 更新用」と理解できる
actorと@MainActorの違い
Swift Concurrency では**actorと@MainActor**という似た名前の仕組みが登場します。
どちらも「直列に処理する」点は共通していますが、守る対象と実行場所が違うのがポイントです。
| 項目 | actor |
@MainActor |
|---|---|---|
| 仕組み | 専用の直列キューで処理を順番に実行 | 「必ずメインスレッドで実行する」と宣言する属性 |
| スレッド | 固定されない(メイン以外もあり得る) | 常に 1 本しかないメインスレッド |
| ゴール | データ競合を防ぐ(同時アクセスを直列化) | UI を安全に扱う(UI更新を保証) |
| 用途 | キャッシュ・DB・共有状態の保護 | ViewModel・UI更新処理・@Published の管理 |
宣言的であること
@MainActorは「メインスレッドに処理を渡す宣言」。
複数の@MainActor処理が同時に呼ばれても、すべて同じメインスレッドに順番待ちで積まれるため、同時実行にはならない。
違いのまとめ
- actor = データを守る
- @MainActor = UI を守る
というイメージです
Actor と SwiftUI の注意点
Actorの中に @StateObject を置くと実行時エラーになります。
これは SwiftUI の状態管理(@StateObject)が View のライフサイクルに強く依存し、
Actor の「並行アクセスを順番処理する仕組み」と相性が悪いためです。
actor MyActor {
@StateObject var viewModel = SomeViewModel() // ❌ 実行時エラー
}
- actor は「共有状態を守る箱」、@StateObject は「Viewごとの状態管理」と役割が違うので混ぜないようにする
- SwiftUI で使うときは View 側に @StateObject を置き、actor にはロジックやデータだけを持たせるのが良い
最後に
await MainActor.run { … } と Task { @MainActor in … }
最初は「どっちも同じでしょ?」と思っていましたが、実際は大きな違いがありました。
- Task { @MainActor in … } → タスク全体をメインスレッドで実行
- await MainActor.run { … } → タスク内部の一部だけメインに切り替える
この違いを知ったのはけっこう衝撃でした。
MIXIのインターンでたくさん知見増えました🤩