Swiftのactorで状態を守るときに見ること

Swift Concurrencyで共有状態を扱うとき、actor が出てきます。

複数のTaskから同じ値を読み書きすると、順番や競合が分かりづらくなります。actorは、その状態へのアクセスを直列化して守るための仕組みです。

公式ドキュメントでも、actorは可変状態への同時アクセスを安全にするものとして説明されています。

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Actors

actorの基本

actor Counter {
    private var value = 0

    func increment() {
        value += 1
    }

    func current() -> Int {
        value
    }
}

actorの外からメソッドを呼ぶときは await が必要です。

let counter = Counter()

await counter.increment()
let value = await counter.current()

actorの内部状態にアクセスするには、そのactorの実行コンテキストに入る必要があります。

なぜawaitが必要なのか

actorは内部の状態を守るため、外からの呼び出しを順番に処理します。

そのため、呼び出し側から見ると待ちが発生する可能性があります。

let value = await counter.current()

await は「ここで一時停止する可能性がある」という印です。

MainActorとの違い

MainActor は、メインスレッド上で実行したい処理に使われます。

@MainActor
final class ViewModel: ObservableObject {
    @Published var count = 0
}

UI更新はMainActorに寄せることが多いです。

一方で、通常のactorは独自の分離領域を持つ共有状態に使います。

使うもの 場面
actor 独自の共有状態を守る
@MainActor UI更新やメインスレッド上の状態

使いどころ

actorは、次のような状態に向いています。

  • 複数Taskから更新されるキャッシュ
  • 同時アクセスされるカウンタ
  • バックグラウンド処理の進捗管理
  • 接続状態やセッション状態

単純なローカル変数までactorにする必要はありません。

まとめ

actorは、Swift Concurrencyで共有状態を守るために使います。

  • actor内部の状態は外から直接触れない
  • actorのメソッド呼び出しには await が必要
  • UI更新は @MainActor
  • 独自の共有状態には通常の actor

Task が増えて状態が散らばってきたら、actorに寄せられるかを見ると整理しやすいです。

関連記事

参考