📱

iOS17で変わるCore Locationのあれこれ

2023/06/26に公開

概要

位置情報のトラッキングや取得に利用されるCore Locationですが、iOS17から以下の変更があったので紹介します。

  • 新しいAPIである CLLocationUpdate が追加され、位置情報取得の並行処理が可能になった
  • バックグラウンドでの挙動が少し変わった

CLLocationUpdateの追加とConcurrency対応

これまで位置情報を取得したい場合、CLLocationManagerDelegateに準拠し didUpdateLocation を通して非同期的に位置情報を取得していました。

そのためシンプルにサッと位置情報だけ得て処理をさせたい場合、Delegate内あるいはイベントを通知させて別に処理させる、または外部ライブラリを利用する必要がありました。

しかしながら今回追加されたCLLocationUpdate.liveUpdatesを利用することでDelegateの呪縛から解放され、並行に処理を記述することができるようになりました🎉

let updates = CLLocationUpdate.liveUpdates()

for try await update in updates {
    guard let location = update.location else { return }
    ...
}

https://developer.apple.com/documentation/corelocation/cllocationupdate/4211321-liveupdates

liveUpdatesが返す CLLocationUpdate.UpdateAsyncSequence に準拠しています。
そのため、ループし続けている間は継続的に位置情報の取得が行われます。

では位置情報の取得をやめたい場合どうするかというと、フラグを挟むなどしてforループから抜けることでCore Location側も勝手に取得を止めてくれます。

for try await update in updates {
    if stopUpdateLocation { break }
    ...
}

バックグラウンドで継続して位置情報を取得させる

CLLocationUpdateを利用中、特に何もしないままアプリをバックグラウンドに遷移させると位置情報の取得が停止してしまうため、バックグラウンド中にいる時も位置情報を継続して取得する方法が用意されています。

WWDCセッションによると、iOS16から追加されたLive Activityを使用することが推奨されています。 ただLive Activityは必須ではなく、対応しないアプリ向けにCLBackgroundActivitySession が利用できます。


WWDC2023 - Discover streamlined location updatesより引用

https://developer.apple.com/documentation/corelocation/clbackgroundactivitysession

以下のコードのように CLBackgroundActivitySession を生成し保持すると、アプリがバックグラウンドに移行した際にステータスバーに位置情報を取得していることを示すインジケータが表示され、バックグラウンドで継続的に位置情報を取得できます。

final class LocationManager {
    private static let backgroundSession = CLBackgroundActivitySession()
}

バックグラウンドで位置情報取得をやめるには

CLBackgroundActivitySessioninvalidate メソッドを呼ぶことでセッションを破棄することができます。
ただし一度 invalidate を呼び出すとそのセッションは無効となり再利用できません。再びバックグラウンドで位置情報を取得したい場合はCLBackgroundActivitySessionを再生成する必要があります。

isStationaryで停止状態を判別する

CLLocationUpdate.Update には位置情報の変化がない、ユーザーの停止状態を判別するプロパティが用意されています。

https://developer.apple.com/documentation/corelocation/cllocationupdate/4211320-isstationary

OS側がしばらく動いていないと判断すると、isStationary にtrueが入ります。この情報を利用することで停止状態の時は位置情報の取得や処理をやめる、といったことが可能になります。

for try await update in updates {
    if update.isStationary {
        ...
    }
}

従来の方法では、CLLocationManagerの pausesLocationUpdatesAutomatically にtrueを入れることで同じようにOS側が位置情報に変化がないと判断した際に取得を停止させることができます。

しかしながらこの方法の場合、位置情報の取得が完全に停止するのでアプリがバックグラウンドのままだとユーザーが移動を再開した時に検知ができず、さらに位置情報の取得が停止することでアプリ側はSuspendedとなっているので復帰ができません。位置情報の取得が停止したことはアプリ側で検知できるので、そのタイミングでユーザーにアプリを立ち上げ直してもらう必要がありました。

その点 isStationary には優位性があり、ユーザーが移動を再開した時はOSが位置情報の配信を再開します。つまりユーザーにアプリを立ち上げ直してもらう必要がなくなります。

ライフサイクルの変化

WWDCセッションの説明によると、バックグラウンドでの位置情報の扱いによってライフサイクルが微妙に変化しています。
isStationary がtrue、しばらくユーザーが移動していないときにアプリがバックグラウンドにいるとSuspendedに移行します。
それじゃあユーザーが移動を再開した時に処理できないじゃんと思うところですが、その場合OSがアプリを自動的にバックグラウンドに復帰させます。


WWDC2023 - Discover streamlined location updatesより引用

これは電力効率の観点からすると良い変更で、位置情報の変化があるときだけ処理をさせるのでリソース利用を最小限にできます。
ただ従来であればバックグラウンドで位置情報を取得中であればその間バックグラウンドでAPIリクエストなど色々処理ができたのですが、今後はそういったことがやりづらくなりました。
既存のバックグラウンドであれこれやっているアプリは設計の見直しを迫られる可能性があります。

良いニュースもあります。バックグラウンドで動作している最中にアプリがクラッシュしたり、ユーザーがアプリを終了させたり、リソースの過剰利用によってOSからKillされた場合であっても、位置情報に変更があればOS側からバックグラウンドに復帰させてくれるケースがあるようです(マジか?)


WWDC2023 - Discover streamlined location updatesより引用

注意点としては復帰した時にすぐ CLBackgroundActivitySession の再生成と liveUpdates の呼び出しが必要になります。サンプルアプリでは didFinishLaunchingWithOptions で処理を行っていたため同じようにAppDelegateで再生成するのが良いかと思います。

従来の方法でもSignificant location changesを利用することでアプリがTerminatedの状態でもOS側が復帰させてくれるケースがありますが、これは場所によっては相当移動する必要(一説には基地局レベル)があってあまり機能していなかったイメージです。
なので今回のように位置情報に変更があればOSがアプリを起こしてくれる仕組みに変わったのはバックグラウンドで位置情報を利用し続けたいアプリにとって大変有難い変更と考えます。

参考

https://developer.apple.com/wwdc23/10180
https://developer.apple.com/documentation/corelocation/adopting_live_updates_in_core_location
https://developer.apple.com/documentation/corelocation/cllocationupdate

Discussion