位置情報アプリ開発者必見！Energy Efficiency Guide for iOS AppsのReduce Location Accuracy and Durationを読んでみた


位置情報を使用するアプリを開発していて、電池消費量が悩みのタネです。
すぐバッテリーが切れればユーザーは不満に思いますし、かといって不適切な場所で位置情報取得をオフにすれば正確な情報をユーザーに提供できません。

いい方法がないものかを思案していましたら、Appleの方でぴったしのドキュメントがありました。

[Reduce Location Accuracy and Duration](https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/LocationBestPractices.html#//apple_ref/doc/uid/TP40015243-CH24-SW1)

これは効率的な電気使用を考えた開発をするための資料の１つでCore Locationについて書かれているものです。

他の電気使用量最適化の例にはネットワーク接続の最適化、グラフィックやアニメーションや動画の影響、通知の最適化、Bluethooth、AppleWatch開発のベストプラクティスなどが書かれていました。

この文章では、Reduce Location Accuracy and Durationにかかれていることをまとめたいと思います。
逐語訳ではなく、意訳と私の補足を随時入れていくつもりです。  
勘違いや間違いがあればご指摘いただければと思います。
また資料に書かれたコードはSwiftのみ抜粋したいと思います。

-------

# 一時的な位置情報更新をリクエスト


アプリがユーザーの場所を一時的に更新するには`requestLocation`メソッドをLocation Managerのインスタンスに対して実行しましょう。コードは次のとおりです。そうするとリクエストが完了した時点で位置情報サービスは停止し他の使用されていない場所では位置情報のハードウェアの電源が落とされます。この方法で更新された位置情報は`locationManager:didUpdateLocations:`デリゲートメソッドを通して送られます。必ず実装してください。

```swift
override func viewDidLoad() {
    // Create a location manager object
    self.locationManager = CLLocationManager()
    
    // Set the delegate
    self.locationManager.delegate = self
}
 
func getQuickLocationUpdate() {
    // Request location authorization
    self.locationManager.requestWhenInUseAuthorization()
    
    // Request a location update
    self.locationManager.requestLocation()
    // Note: requestLocation may timeout and produce an error if authorization has not yet been granted by the user
}
 
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    // Process the received location update
}



```

`requestLocation`メソッドはiOS9から使用できます。

# 使ってないなら位置情報サービスを止める

カーナビの様な道案内アプリでない限り、多くのアプリは常時位置情報を更新する必要はありません。必要なときだけ、位置情報サービスをオンにします。位置情報が更新されたらオフにします。自動車にユーザーが乗っているのでなければ位置情報が更新されないことはなんの問題にもなりません。位置情報の更新が必要になったら位置情報サービスをスタートすれば良いのです。

スタンダートな位置情報の更新を止めるには`stopUpdatingLocation`メソッドをLocation Managerインスタンスに対して実行します。

```swift
func getLocationUpdate() {
    // Create a location manager object
    self.locationManager = CLLocationManager()
    
    // Set the delegate
    self.locationManager.delegate = self
    
    // Request location authorization
    self.locationManager.requestWhenInUseAuthorization()
    
    // Start location updates
    self.locationManager.startUpdatingLocation()
}
 
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    // Get a fix on the user's location
    ...
        
        // Stop location updates
        self.locationManager.stopUpdatingLocation()
}

```

# できるだけ、標準的な位置情報の更新頻度を下げる
標準的な位置情報更新では、数メートルから数キロメートルの範囲の精度を指定できます。これは`desiredAccuracy`プロパティをLocation Managerインスタンスに対して設定します。
必要以上に高い精度を要求すると、ハードウェアへの電気使用量が増加し、余分に電力が消費されます。
本当にユーザーの位置情報を数メートル単位で把握しなければならないのでない限り、この精度を最高(`kCLLocationAccuracyBest`)や10メートル(`kCLLocationAccuracyNearestTenMeters`)に設定しないでください。
またCore Locationは一般的に、要求したよりも正確なデータを提供することに注意してください。
たとえば、3キロメートル（`kCLLocationAccuracyThreeKilometers`）の精度レベルを指定すると、100メートル程度の精度が得られることがあります。

デフォルトでは、iOS端末の標準の位置情報の更新は、最高の精度(`kCLLocationAccuracyBest`)で実行されます。アプリの要件に合わせてこれらの設定を変更してください。そうしないと、アプリケーションが不必要にエネルギーを浪費します。


```swift

func getLocationUpdate() {
    // Create a location manager object
    self.locationManager = CLLocationManager()
    
    // Set the delegate
    self.locationManager.delegate = self
    
    // Request location authorization
    self.locationManager.requestWhenInUseAuthorization()
    
    // Set an accuracy level. The higher, the better for energy.
    self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
    
    // Start location updates
    self.locationManager.startUpdatingLocation()
}
```


# バックグラウンドで位置情報更新が起こった時に自動一時停止とアクティビティタイプを指定する

iOSアプリがバックグラウンド上で位置情報を監視し続けるには、XcodeのProject > Capabilitiesパネルからバックグラウンドモードをオンにします。Location updatesを選択してください。

![xcode_project_capabilities_backgroundmodes_locationupdates_2x.png](https://qiita-image-store.s3.amazonaws.com/0/31587/66e8dbe6-0e8b-0283-f5f7-ad3b661ad8aa.png "xcode_project_capabilities_backgroundmodes_locationupdates_2x.png")

iOS9以降はデプロイターゲットに関係なく、バックグラウンドで位置情報更新を受信するにはLocation Managerインスタンスに対して`allowsBackgroundLocationUpdates`プロパティをtrueにしてください。
デフォルトではfalseです。そして、バックグラウンドで位置情報の更新をアプリがリクエストするまではfalseのままにしておきましょう。


電力を節約するために、Location Managerインスタンスの`pausesLocationUpdatesAutomatically`プロパティがtrueに設定されていることを確認してください。

`activityType`プロパティをセットして、Core Locationがアプリがどんな活動状態で実行されているのか知らせてください。例えばフィットネスのトラッキングやカーナビゲーションで実行されているなどです。

--------
### [CLActivityType](https://developer.apple.com/reference/corelocation/clactivitytype)
位置情報更新に関連した活動タイプの定数

以下の４つ状態を持つenumで定義されている

* other
	* 不明な活動タイプで実行されている
* automotiveNavigation
	* 自動車の位置変化を追跡するための車両ナビゲーションで実行されている。このタイプがLocation Managerにセットされていると、車両が長時間移動していない場合に位置更新が一時停止されることがある
* fitness
	* ウォーキングやランニング、サイクリングなどのフィットネスを計測場合に使用するタイプ。ユーザがある期間にわたって有意な距離を移動しないときに位置情報更新を停止する。
* otherNavigation
	* 自動車関連ではない他のタイプの車両ナビゲーションのための動きの場合に使用するタイプ。ボート、列車、または飛行機によるナビゲーションを追跡する場合に使用します。歩行者ナビゲーションのトラッキングにはこのタイプを使用しないでください。乗り物ががある期間にわたって著しい距離を移動しない場合には、位置情報更新を停止する。

---------

これらの設定を指定すると、ロケーション・マネージャーがロケーション更新を実行するのに最適な時間を判断するのに役立ちます。

自動停止を有効にした場合でも、ロケーションの更新が不要になったときに、Location Managerインスタンスの`stopUpdatingLocation`メソッドを呼び出すことを忘れないでください。

```swift

func startBackgroundLocationUpdates() {
    // Create a location manager object
    self.locationManager = CLLocationManager()
    
    // Set the delegate
    self.locationManager.delegate = self
    
    // Request location authorization
    self.locationManager.requestWhenInUseAuthorization()
    
    // Set an accuracy level. The higher, the better for energy.
    self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
    
    // Enable automatic pausing
    self.locationManager.pausesLocationUpdatesAutomatically = true
    
    // Specify the type of activity your app is currently performing
    self.locationManager.activityType = CLActivityTypeFitness
    
    // Enable background location updates
    self.locationManager.allowsBackgroundLocationUpdates = true
    
    // Start location updates
    self.locationManager.startUpdatingLocation()
}
 
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    // Perform location-based activity
    ...
        
        // Stop location updates when they aren't needed anymore
        self.locationManager.stopUpdatingLocation()
    
    // Disable background location updates when they aren't needed anymore
    self.locationManager.allowsBackgroundLocationUpdates = false
}

```

# バックグラウンドで実行する場合位置更新を延期する
GPSハードウェアを搭載した端末では、アプリがバックグラウンドのときに位置情報の配信を遅らせることができます。
例えばハイキングをする場合のユーザー位置を追跡するフィットネスアプリでは、ユーザーが一定の距離を移動するか、一定の期間が経過するまで更新を延期する可能性があります。その後、一度にすべての更新を処理できます。
更新を遅らせるには、Location Managerインスタンスの`allowDeferredLocationUpdatesUntilTraveled：timeout：`メソッドを呼び出し、次のロケーション更新が受信されるまでに経過する可能性のある距離と時間を渡します。
通常、このメソッドは`locationManager：didUpdateLocations：delegate`メソッドで呼び出され、遅延ロケーション更新が受信されたときに、必要に応じて再度遅延させます。

位置情報更新の遅延を自身すると、` locationManager:didFinishDeferredUpdatesWithError:`メソッドも呼び出されます。アプリは、これを使用して、延期距離と時間を増減するなど、それに応じて行動を調整することができます。

```swift
func startHikeLocationUpdates() {
    // Create a location manager object
    self.locationManager = CLLocationManager()
    
    // Set the delegate
    self.locationManager.delegate = self
    
    // Request location authorization
    self.locationManager.requestWhenInUseAuthorization()
    
    // Specify the type of activity your app is currently performing
    self.locationManager.activityType = CLActivityTypeFitness
    
    // Start location updates
    self.locationManager.startUpdatingLocation()
}
 
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    // Add the new locations to the hike
    self.hike.addLocations(locations)
    
    // Defer updates until the user hikes a certain distance or a period of time has passed
    if (!deferringUpdates) {
        distance: CLLocationDistance = hike.goal - hike.distance
        time: NSTimeInterval = nextUpdate.timeIntervalSinceNow()
        locationManager.allowDeferredLocationUpdatesUntilTraveled(distance, timeout:time)
        deferringUpdates = true;
    } }
 
func locationManager(manager: CLLocationManager, didFinishDeferredUpdatesWithError error: NSError!) {
    // Stop deferring updates
    self.deferringUpdates = false
    
    // Adjust for the next goal
}

```

# 特定の地域または場所へのロケーションの更新を制限する

あるアプリでは、継続的に位置情報を更新する必要はなく、ユーザーが特定の場所にいるかどうかがわかればいいという場合があります。たとえば、食料雑貨のアプリは、ユーザーが店に近づくたびに新しいクーポンを表示することがあります。あなたのアプリに情報を提供することができるいくつかのCore Location APIがあります。


これから話すモニタリング機能は、入退出通知のみを提供します。通知が受信されたときにユーザーの実際の場所を判断するには、`requestLocation:`か`startUpdatingLocation:`メソッドをLocation Managerインスタンスに対して実行します。これらのモニタリングには、認証ステータスが`kCLAuthorizationStatusAuthorizedAlways`も必要です。

## 領域やビーコンをモニタリングする

Core Locationは２つの方法で特定の地域へのユーザーの出入りを検出します。

* 地理的領域の監視は、緯度経度の指定された場所の入退出通知を提供します。
* ビーコンモニタリングは、ユーザがiBeacon情報をアドバタイズしているBLEデバイスの範囲内にある場合に、入出通知を提供する。


## 訪問監視

ユーザーが頻繁または長時間訪問する特定の場所の入退出の通知を受け取ることができます。例えば自宅や仕事場、お気に入りの喫茶店などです。
訪問監視を始めるには、Location Managerのデリゲートメソッドを実装し`startMonitoringVisits`メソッドを実行します。このメソッドを実行すると一度だけでなくアプリ内全ての訪問場所の更新が可能になります。
有効にすると、訪問イベントがデリゲートメソッド`locationManager:didVisit:`を通して呼ばれます。
**訪問イベントが配信されたときにアプリが実行されていない場合、アプリは自動的に再起動されます。**
訪問情報の更新が必要なくなったら、`stopMonitoringVisits`メソッドを実行して停止してください。


```swift
func startVisitMonitoring() {
    // Create a location manager object
    self.locationManager = CLLocationManager()
    
    // Set the delegate
    self.locationManager.delegate = self
    
    // Request location authorization
    self.locationManager.requestAlwaysAuthorization()
    
    // Start monitoring for visits
    self.locationManager.startMonitoringVisits()
}
 
func stopVisitMonitoring() {
    self.locationManager.stopMonitoringVisits()
}
 
func locationManager(manager: CLLocationManager, didVisit visit: CLVisit!) {
    // Perform location-based activity
    ...
}
```

# シグニフィカントチェンジロケーションを登録する。

GPSレベルの精度がアプリにとって重要でない場合で、位置情報の追跡を継続する必要はなく、領域や訪問の監視も適切ではない場合は標準的な位置情報の代わりとしてシグニフィカントチェンジロケーションを使用することが出来る。

領域と訪問の監視は多くの場合それで十分であり、シグニフィカントチェンジロケーションを使用する場合は考慮が必要。
シグニフィカントチェンジロケーションを使用する場合、効果的に使用されないと、実際にエネルギー使用量が増加する可能性があることに注意してください。

* 場所の変更が大きく発生していない場合でも、シグニフィカントチェンジロケーションの更新により、システムとアプリは最低でも15分ごとに復帰します。
* シグニフィカントチェンジロケーションの更新は停止するまで24時間連続して実行されます。


シグニフィカントチェンジロケーションで更新するには`startMonitoringSignificantLocationChanges`メソッドをLocation Managerインスタンスに対して実行します。終わったら、`stopMonitoringSignificantLocationChanges`メソッドを実行します。

シグニフィカントチェンジロケーションの認証ステータスは`kCLAuthorizationStatusAuthorizedAlways`が必要です。


```swift
func startSignificantChangeLocationUpdates() {
    // Create a location manager object
    self.locationManager = CLLocationManager()
    
    // Set the delegate
    self.locationManager.delegate = self
    
    // Request location authorization
    self.locationManager.requestAlwaysAuthorization()
    
    // Start significant-change location updates
    self.locationManager.startMonitoringSignificantLocationChanges()
}
 
func locationManager(manager: CLLocationManager, didFinishDeferredUpdatesWithError error: NSError!) {
    // Perform location-based activity
    ...
        
        // Stop significant-change location updates when they aren't needed anymore
        self.locationManager.stopMonitoringSignificantLocationChanges()
}
```