登場人物
- 先生:プログラミング部顧問の先生
- はるか:高校2年生。最近 Swift の勉強を始めた
- あきと:高校3年生。プログラミング部の先輩
1. はじまり
はるか「先生、iPhone で位置情報を取得する方法を教えてもらったんですけど、CoreLocation ってメインスレッドとは限らないところで呼ばれることがあるんですか?」
先生「いい質問だね。CoreLocation のデリゲートメソッドは、必ずしもメインスレッドで呼ばれないことがあるんだ。並行処理の観点で注意が必要なんだよ。」
あきと「あ、Swift Concurrency で actor を使えばスレッドの混乱を防げる…っていう話ですね!」
先生「そうそう。特に @MainActor を使う場合、CoreLocation からのコールバックがメインスレッドじゃないときに直接 UI 関連のプロパティを触ると問題が起きるんだ。」
2. CoffeeLocationDelegate のクラスを見てみよう
先生「じゃあ実際のコード例を見よう。CoffeeLocationDelegate というクラスを作って、CoreLocation からの位置情報を受け取るときにどうするかを紹介するよ。」
以下のようなコードを書くイメージだ。
@MainActor
class CoffeeLocationDelegate: NSObject, CLLocationManagerDelegate {
var location: CLLocation?
var manager: CLLocationManager?
override init() {
super.init()
manager = CLLocationManager()
manager?.delegate = self
manager?.startUpdatingLocation()
}
nonisolated func locationManager(
_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]
) {
Task {
await MainActor.run {
self.location = locations.last
}
}
}
}
3. 先生と一緒にコードを解読
@MainActor
はるか「このクラス全体に @MainActor ってついてますね。これはなんですか?」
先生「@MainActor は、『このクラスはメインアクター上で動きます』という宣言だよ。UI の更新を行う場合はメインアクターに乗せておくと安全なんだ。」
nonisolated
あきと「でも、その中の locationManager(_:didUpdateLocations:) というデリゲートメソッドには nonisolated って書いてあります。これは?」
先生「nonisolated は、『このメソッドはアクターに属さない』と宣言しているんだ。CoreLocation は位置情報を別のスレッドで処理して、いつどのスレッドからコールバックを呼ぶか分からないからね。もし @MainActor を付けたままだと、『必ずメインアクター上で呼ばれる』とコンパイラが思い込んでしまい、実際に別スレッドから呼ばれたときに並行性のエラーや警告が出ちゃうんだ。そこで、nonisolated を付けて『ここはアクター分離されていません』と伝えて、別スレッドから呼ばれても大丈夫にしてるんだよ。」
MainActor.run
はるか「でも、そのメソッドの中で Task { await MainActor.run { ... } } ってやってますよね?」
先生「そこがポイント! nonisolated でアクター外から呼ばれるのはいいんだけど、このクラスのプロパティ location を更新するときにはメインアクターに戻らないといけない。だから Task と await MainActor.run を使って、実際にメインアクターへ切り替えているんだよ。」
4. どうしてこんな面倒なことをするの?
あきと「正直、MainActor.assumeIsolated っていうのを使って“今は絶対にメインアクター上だ!”って宣言しちゃう手もあるんですよね?」
先生「うん。MainActor.assumeIsolated は確かにコンパイラを黙らせる手段にはなる。でも、もし本当にメインスレッド以外で呼ばれたら、並行性バグを起こす危険があるんだ。MainActor.run はスレッドをちゃんとメインアクターに移してくれるから安全なんだよ。」
はるか「なるほど…。無理やり『大丈夫!』っていうより、ちゃんと安全なやり方でメインアクターに戻すほうが安心ですね。」
5. まとめ
先生「最後にポイントをおさらいしよう。全部で5つあるから整理してね!」
- CoreLocation のデリゲートはメインスレッドで呼ばれる保証がない。
- MainActor クラスでデリゲートを扱う場合、nonisolated を使ってアクター外から呼ばれても大丈夫にする。
- メインアクター上のプロパティを更新するときは Task { await MainActor.run { ... } } で安全に切り替える。
- MainActor.assumeIsolated は自己責任での宣言。CoreLocation のように実際にスレッドが不定な場合は危険。
先生「以上が、CoffeeLocationDelegate を例にした CoreLocation × Swift Concurrency の解説だよ。ぜひ参考にして、安全に位置情報を扱ってみてね。」
はるか「ありがとうございます!実際にコードを書いて動かしてみます!」
あきと「自分も改めて確認できました。ありがとうございます!」
先生「じゃあみんな、Happy Coding!」