Swift で UI を操作するコードは、同時にいろいろな処理が動いていても メインスレッド だけで動かしたいですよね。そんなときに使われるのが @MainActor
です。ですが、バックグラウンド処理から画面を更新したいとき、いったいどうやって「メインアクター」に移動すればいいのでしょう? それを実現するのが 「メインアクターへのジャンプ」 です。
本記事では、中学生にもわかるように、できるだけやさしく解説してみます。
メインアクターとは?
- アクター とは、Swift Concurrency でデータ競合(データが同時に書き換えられてしまうバグ)を防ぐための仕組みです。
- メインアクター(@MainActor) は、UI を扱う処理を「メインスレッド」でだけ動かすためのアクターです。
iOS のアプリ開発では、UIKit や SwiftUI で画面を更新する処理は、必ずメインスレッド で行う必要があります。Swift Concurrency では、@MainActor
を使うことで、そのメソッドやプロパティを メインアクターに属する と宣言できます。
@MainActor
func updateUI() {
// メインスレッドで安全に画面を更新するコード
}
こう書くと、Swift は「この関数はメインアクター(メインスレッド)でしか呼んじゃダメ!」とチェックしてくれます。
メインアクターへのジャンプとは?
バックグラウンドからメインへ切り替える
たとえば、ネットワーク通信や重い計算をしている「バックグラウンド処理」から画面を更新したいとき、直接メインアクターの関数を呼ぼうとすると Swift Concurrency が怒ってきます。
「いまバックグラウンドにいるのに、メインアクターの関数を同期的に呼んじゃダメ!」
そうならないように、await
を使ってアクターを切り替える のが「メインアクターへのジャンプ」です。
@MainActor
func updateUI() {
// 画面を更新するコード
}
func doSomethingInBackground() async {
// バックグラウンドで処理
// ...
// 画面を更新したい! → メインアクターにジャンプ
await updateUI()
}
await updateUI()
と書くことで、呼び出し前に メインアクターへ切り替わって、安全に画面更新ができるようになります。
具体的なジャンプ方法
1. await
付きで呼び出す
単純にメインアクターな関数を await
で呼び出す方法です。
@MainActor
func updateUI() {
print("画面を更新します!")
}
func backgroundTask() async {
// バックグラウンド処理
// ...
await updateUI() // メインアクターへジャンプして画面更新
}
2. await MainActor.run { ... }
複数の処理をまとめてメインアクターで実行したいときは、MainActor.run
が便利です。
ブロックの中はすべてメインアクター上になるので、UI 更新コードをまとめられます。
func backgroundTask() async {
// 重い処理...
await MainActor.run {
// ここはメインアクター上になる
// UI関連のコードをまとめて書ける
updateLabel()
showMessage()
}
}
3. Task { @MainActor in ... }
新しくタスクを作って、そのタスクがメインアクターで動くようにする方法です。
func doSomething() {
Task { @MainActor in
// ここはメインアクター上で動く
updateUI()
}
}
「いまこの瞬間にメインアクターで実行したいコード」を書く場合に使えます。
なんで安全なの?
- メインアクター は、画面を変える処理を順番に実行してくれます。複数の処理が同時に走って画面がごちゃごちゃになるのを防ぐ仕組みです。
-
await
は、「ここでいったん処理を止めて、メインアクターに切り替わるのを待つ」印です。スレッドをブロックするのではなく、非同期的に順番待ちします。
もしメインアクターじゃないところから直接 UI をいじると、データ競合やクラッシュの原因になります。Swift Concurrency はこうしたミスをコンパイルエラーにしてくれるため、安全性が高いわけです。
気をつけたいポイント
-
同期的に呼び出せない
メインアクターな関数は、メインアクター外から「同期的」に呼び出すことはできません。必ずawait
を付けて呼び出すか、同じメインアクター内で呼ぶ必要があります。 -
ジャンプを多用しすぎない
いろんなところで頻繁にawait
付き呼び出しをすると、コードが行ったり来たりでわかりにくくなる場合も。なるべくMainActor.run { ... }
でまとめて書くなど、設計を工夫しましょう。 -
MainActor.assumeIsolated
は最終手段
「ここは絶対メインアクター上だよ!」と無理やり教える方法もありますが(assumeIsolated
)、もし実際はメインアクターじゃなかったらクラッシュします。なるべく@MainActor
やawait
を使った正式な方法をおすすめします。
まとめ
- Swift で UI を更新するコードは、メインアクター に隔離する(
@MainActor
)。 - バックグラウンド処理から画面を更新したい場合は、「メインアクターへのジャンプ」 が必要。
- ジャンプの方法はいろいろあり、
await
付き呼び出し、MainActor.run { ... }
、Task { @MainActor in ... }
などがある。 - こうすることで、UI がごちゃごちゃになる競合を防ぎ、安全に画面を更新できる。
「メインアクターへジャンプ」 を正しく使って、非同期処理と画面更新を上手に両立させていきましょう!