1.MainActor
って何?どうして必要なの?
生徒「先生、Swift のコードで @MainActor
っていうのを見かけたんですけど、これは何なんでしょうか?」
先生「MainActor
は、アプリの画面(UI)を操作するコードが“メインスレッド”で安全に実行されるようにする仕組みなんだよ。Swift Concurrency では、データの競合を防ぐためにアクター(Actor)と呼ばれる単位で並行性を管理するでしょ? その中でも MainActor
は、内部的には DispatchQueue.main
を使いながら、UI 操作が必ずメインスレッドで実行されるように守ってくれる特別なアクターなんだ。」
生徒「なるほど。UI を更新するときに、メインスレッド以外で操作したら不具合が起こるって聞いたことがあります。MainActor
を使うと、そういう問題を防げるんですね!」
先生「そう。UI 系のコードを安全に書くためには、@MainActor
を使うのが基本になるね。」
2.MainActor
の適用方法
生徒「でも具体的に MainActor
はどうやって使うんですか? たとえばクラスやメソッド単位に付けるとか、いろいろ方法があるって聞きました。」
先生「うん、MainActor
を“どこに”付けるかがポイントだね。たとえば、クラス全体を @MainActor
にしてしまうと、そのクラスのプロパティやメソッドはすべてメインスレッド上で動くように“暗黙的”に指定される。もし一部のメソッドだけメインスレッドで動かしたいなら、プロパティやメソッドごとに @MainActor
を付けられるよ。」
2-1. 型全体へ @MainActor
を適用する
@MainActor
class UserDataSource {
// ここで宣言されるプロパティ・メソッドは
// メインスレッド(MainActor)で動く
var user: String = ""
func updateUser() {
// UI 更新などを安全に行える
}
// ただし nonisolated を使うと、MainActor から分離可能
nonisolated func sendLogs() {
// ここは MainActor じゃなく呼び出せる
}
}
生徒「@MainActor
をクラスの頭につけると、その中のプロパティやメソッドは全部メインスレッド実行の対象になるんですね。でも nonisolated
で解除って何ですか?」
先生「nonisolated
は『このメソッドはアクター(今回だと MainActor)に隔離されないで呼び出せる』という意味だよ。メインスレッド以外でも呼び出す必要がある処理の場合、こう書けば “この部分だけ” アクターの縛りを外すことができるんだ。」
3.プロパティやメソッド個別に @MainActor
を付ける
先生「型全体を @MainActor
にしなくても、プロパティやメソッド単位で付けることもできる。たとえばこんな感じ。」
struct Mypage {
@MainActor var info: String = ""
@MainActor
func updateInfo() {
// ここは MainActor で動作
}
func sendLogs() {
// ここは普通の関数で、MainActor は適用されない
}
}
生徒「ふむふむ。info
プロパティと updateInfo()
にだけ @MainActor
付いてますね。これなら使い分けられそう。」
4.MainActor
で UI データを更新してみる
生徒「実際に UI データを更新するときはどうするんですか?」
先生「代表的なのは、ViewModel と呼ばれるクラスを丸ごと @MainActor
にして、その中で @Published var text
みたいなプロパティを持って、UI に表示する文字列を管理するやり方。非同期でサーバーからデータ取ってきて、それをメインスレッドでUIに反映する流れが分かりやすいんだ。」
@MainActor
final class ViewModel: ObservableObject {
@Published private(set) var text: String = ""
// サーバー通信はメインスレッドでやらなくていいので nonisolated にする
nonisolated func fetchUser() async {
// サーバー問い合わせの通信を想定
// ここは await とか時間のかかる処理を非同期でやる
// もし中で text を更新しようとするとエラーになる
// 結局、この非同期メソッドの中では text に代入ができない
// 代わりに「値を返して、メインスレッドに戻ってから代入する」必要がある
}
func didTapButton() {
Task {
text = ""
// いったんクリアして
// 別メソッドで値を取得
text = await fetchUser()
}
}
private func waitOneSecond(with string: String) async -> String {
try? await Task.sleep(nanoseconds: 1 * NSEC_PER_SEC)
return string
}
}
生徒「あ! nonisolated func fetchUser()
の中で text
を直接書き換えようとするとエラーが出るんですね。『Property 'text' isolated to global actor 'MainActor' cannot be mutated from a non-isolated context』ってやつ。」
先生「そうそう。@MainActor
で保護されてる text
を、nonisolated
な文脈からそのまま書き換えるとデータ競合(データレース)が起こるかもしれないから、コンパイラがエラーで止めてくれるんだよ。だから fetchUser()
はサーバーから名前だけ返して、実際に text
に代入するのはメインスレッドに戻ってからにする、というやり方が必要になる。」
5.「値を返す」形に修正してみる
生徒「たしかに fetchUser()
の中で text
を更新すると怒られるなら、値を返す関数にすればいいんですね。」
先生「そう。具体的にはこうすればコンパイルが通るようになる。」
@MainActor
final class ViewModel: ObservableObject {
@Published private(set) var text: String = ""
// nonisolated だが、String を返す
nonisolated func fetchUser() async -> String {
return await waitOneSecond(with: "タカシ")
}
func didTapButton() {
Task {
text = ""
text = await fetchUser()
}
}
private func waitOneSecond(with string: String) async -> String {
try? await Task.sleep(nanoseconds: 1 * NSEC_PER_SEC)
return string
}
}
先生「これなら fetchUser()
が文字列を返すだけだから、サーバー通信部分はメインスレッドに縛られないで実行できるし、didTapButton()
の中で text
をちゃんとメインアクターコンテキストにいる状態で更新できるよ。」
生徒「なるほど! つまり、データ競合を起こさないために、アクターの外側(nonisolated
なとこ)からアクターの中のプロパティに変更を加えないようにする、っていう流れですね。」
先生「そういうこと。Swift Concurrency はコンパイラレベルでチェックが入るから、ルールに反したらビルドエラーで教えてくれる。ちゃんとルールに沿えば UI 更新と非同期処理をきれいに分けられるわけだ。」
6.MainActor
と nonisolated
を組み合わせるメリット
生徒「結局これってどういうメリットがあるんでしょうか?」
先生「たとえば、UI に表示する部分は @MainActor
で守ってあるから、画面の更新を安全に並列処理できる。だけどサーバー通信みたいにメインスレッドじゃなくていい部分は nonisolated
で外に逃がすことで、メインスレッドをブロックせずにすむ。こうするとアプリがカクつきにくくなるし、スムーズに動くようになるよ。」
生徒「なるほど。UI が止まるとユーザ体験が悪いですもんね。そういった点でも MainActor
と nonisolated
の使い分けは重要なんですね。」
7.まとめ
生徒「今日習ったことを整理すると――」
-
@MainActor
とは?- UI 操作コードを安全にメインスレッドで実行する仕組み。
- クラスや構造体をまるごと
@MainActor
にするか、必要なプロパティやメソッドにだけ@MainActor
を付ける方法がある。
-
nonisolated
の意味- そのメソッドやプロパティが「アクターに隔離されずにアクセス可能」になる。
- ただし
@MainActor
で保護されているプロパティを書き換えようとすると、コンパイルエラーになる。
-
UI 更新の流れ
-
ViewModel
を@MainActor
で保護し、UI 表示に使うプロパティ(例:text
)を用意する。 - サーバーなど時間のかかる処理は
nonisolated
なメソッドで行い、アクター外で値を受け取る。 - 実際の UI プロパティ更新は
MainActor
上に戻ってから行う。
-
-
コンパイラエラーによる保護
-
MainActor
に属するプロパティを誤って他所から書き換えようとすると、コンパイルエラーで教えてくれる。 - スレッドセーフティを言語レベルでサポートしている。
-
-
メリット
- メインスレッドをブロックせずに UI を更新できる。
- データ競合を防ぎながら、わかりやすいコードが書ける。
生徒「ありがとうございます。さっそく自分のアプリでも実践してみますね!」