4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SwiftのActorまとめ

4
Last updated at Posted at 2025-09-30

概要

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のインターンでたくさん知見増えました🤩

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?