Caution
お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。
Actor / データ競合の防止
Swiftの『actor』は、共有された変更可能な状態へのデータ競合を防ぐための型です。actor のプロパティやメソッドへのアクセスは自動的にシリアライズされます。
構文
// actor の定義(class と似た構文)
actor アクター名 {
var 状態: 型
init(初期値: 型) {
self.状態 = 初期値
}
// actor 内のメソッドは自動的に隔離される
func メソッド() {
状態 = 新しい値
}
}
// actor へのアクセスは await が必要
let result = await アクターインスタンス.メソッド()
// @MainActor: UI スレッドで実行することを保証
@MainActor
func updateUI() {
// UIの更新
}
構文一覧
| 構文 | 概要 |
|---|---|
| actor 名前 { } | actor を定義します。class と似た構文で、継承はできません。 |
| await actor.メソッド() | actor のメソッドを外部から呼び出す際は await が必要です。 |
| nonisolated func | actor の隔離から除外したメソッドを定義します(スレッドセーフな処理のみ)。 |
| @MainActor | クラス・関数・プロパティをメインスレッドで実行させます。 |
| MainActor.run { } | 任意のコードをメインスレッドで実行します。 |
サンプルコード
import Foundation
// actor でスレッドセーフなカウンター
actor Counter {
private var count = 0
func increment() {
count += 1
}
func getCount() -> Int {
return count
}
// nonisolated: actor 隔離が不要なメソッド
nonisolated func description() -> String {
return "Counter actor"
}
}
let counter = Counter()
// 並列で1000回インクリメント
await withTaskGroup(of: Void.self) { group in
for _ in 1...1000 {
group.addTask {
await counter.increment()
}
}
}
let total = await counter.getCount()
print("最終カウント: \(total)") // 必ず 1000
// nonisolated メソッドは await 不要
print(counter.description())
// @MainActor: メインスレッドでの UI 更新
@MainActor
class ViewModel {
var title: String = "読み込み中..."
func loadData() async {
// バックグラウンドで処理
let result = await Task.detached { "データ取得完了" }.value
// @MainActor なので UI を直接更新できる
title = result
print("タイトル: \(title)")
}
}
Task {
let vm = await ViewModel()
await vm.loadData()
}
概要
通常のクラスでは複数のスレッドから同時にプロパティにアクセスするとデータ競合が発生する可能性がありますが、actor を使うと自動的にシリアライズされて安全です。
actor はクラスと似ていますが継承できません。プロトコルには準拠できます。actor の内部から自身のプロパティやメソッドには await なしでアクセスできますが、外部からのアクセスには必ず await が必要です。
async/await の基本についてはasync / await の基本を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。