メインスレッド以外でCLLocationManagerを生成しないほうが良い
要約
- CLLocationManagerをメインスレッド以外で生成するとCLLocationManagerDelegateが呼ばれなくなるので気をつけよう
- ライブラリを扱う場合はREADMEをしっかり読みましょう
経緯
Core Locationで位置情報を取得するには、メソッド一つ呼んでクロージャ経由で位置情報が返ってくる…というわけではなく、CLLocationManagerDelegateに準拠したオブジェクトを用意し、didUpdateLocations
メソッドに通知される位置情報をPublisherなどで別に通知させる必要があったりして処理フローとしてはやや複雑です。
現在個人開発アプリをSwift Concurrency対応に書き直しており、Core LocationもDelegate経由ではなくConcurrencyで扱えるようにしたい。そこでAsyncLocationKitというライブラリを導入し使い始めました。このライブラリを使うことで、以下のように位置情報を並行処理で扱うことが可能となります。
for await event in await asyncLocationManager.startUpdatingLocation() {
switch event {
case .didUpdateLocations(let locations):
...
default: break
}
}
利用してみたは良いものの、位置情報の権限状態や位置情報サービスの有効無効に関しては取れますが、位置情報の取得を始めても一向にデータが流れてこない事態が起きていました。
あーでもないこーでもないと色々ガチャガチャやっていたところ、Appleのドキュメントに以下のような記載があるのを見つけました。
Core Location calls the methods of your delegate object using the RunLoop of the thread on which you initialized the CLLocationManager object. That thread must itself have an active RunLoop, like the one found in your app’s main thread.
https://developer.apple.com/documentation/corelocation/cllocationmanager
つまり、CLLocationManagerを初期化した時のスレッドのRunLoopを利用してCLLocationManagerDelegateへ通知を行っているので、メインスレッドのようなアクティブなRunLoopを持つスレッドでの初期化が必要になる。
今回のケースでは、たまたまAsyncLocationKitの生成(= CLLocationManagerの生成)をMainActor以外で行っており通知が来なかった、ということでした。
そしてAsyncLocationKitのREADMEにもそれらしい記載があったことに気づいたのでした…
⚠️ Initialize AsyncLocationManager only synchronously on MainThread
https://github.com/AsyncSwift/AsyncLocationKit/blob/main/README.md
Discussion