[iOS] アプリが終了している状態でもヘルスケアデータの更新を検知できる


## はじめに

iOSでは、iOSや各アプリから"ヘルスケア"にいろいろなヘルスケアデータが書き込まれます。
iOSアプリはヘルスケアデータをHealthkit経由で読み書きが可能で、またデータの更新を監視することができます。

そして更新の監視は**アプリが終了している状態でも有効にすることができる**、というのがこの記事の主旨です。

| アプリの状態 | ヘルスケアデータの更新検知 |
|:------------------:|:------------------:|
| フォアグラウンドで実行されている | 可能 |
| 実行されているがフォアグラウンドにない | 可能 |
| **アプリが終了している** | **可能** ←ここ |


↓のサンプルでは、アプリが終了している状態でヘルスケアへのデータ書き込みを検知して、アプリからローカル通知を送信してます。
※最初左右にスワイプしてるのはヘルスケア以外にアプリ起動してないアピールです

| サンプル |
|:------------------:|
| <img width="320" alt="sample.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72194/109b3bc7-a766-ee39-7656-c830bd13cb36.gif"> |

## 実装方法

1. Healthkitで任意の種類のヘルスケアデータに対して `HKObserverQuery` を実行する
2. `enableBackgroundDelivery(for:frequency:withCompletion:)` を実行する

```swift
let objectType = HKSampleType.quantityType(forIdentifier: .bodyMass)!
let query = HKObserverQuery(sampleType: objectType, predicate: nil, updateHandler: { query, completionHandler, error in
    // 更新検知
})
HKHealthStore().execute(query)

// バックグランドでのヘルスケアデータの更新検知を有効にする
HKHealthStore().enableBackgroundDelivery(for: objectType, frequency: .immediate, withCompletion: { success, error in
})
```

- 検知できる更新タイミングは`enableBackgroundDelivery(for:frequency:withCompletion:)`の引数として渡す [HKUpdateFrequency](https://developer.apple.com/documentation/healthkit/hkupdatefrequency) で指定した期間に最大1回
  - 指定できる期間は `immediate` `hourly` `daily` `weekly` 、ただし特定のヘルスケアデータ（歩数など更新頻度が非常に高いものなど）は `hourly` 以上の頻度でしか検知できない

詳しくは[公式ドキュメント](https://developer.apple.com/documentation/healthkit/hkhealthstore/1614175-enablebackgrounddelivery)参照。

## サンプル

以下、体重データの更新を検知してローカル通知を送るサンプルです。

サンプルの動作環境: `Xcode11.2.1` `iOS13.2` 

### プロジェクト設定

1. ヘルスケアからデータの読み出しを許可するため、Info.plistに `Privacy - Health Share Usage Description` をセットする
2. project -> target -> Signing&Capabilities で以下を設定する
  - `HealthKit` を有効にする
  - `PushNotifications` を有効にする（このサンプルでローカル通知を送るため）
<img width="787" alt="スクリーンショット 2019-12-05 0.23.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72194/7afa96c6-0e21-7696-9e96-7682bf41dc94.png">



### サンプルコード

```swift:AppDelegate.swift
import UIKit
import UserNotifications
import HealthKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // プッシュ通知を有効にする
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge], completionHandler: { _, _ in
            DispatchQueue.main.async {
                UIApplication.shared.registerForRemoteNotifications()
            }
        })
        
        // ヘルスケアから体重データの読み込みを許可
        let objectType = HKSampleType.quantityType(forIdentifier: .bodyMass)!
            
        HKHealthStore().requestAuthorization(toShare: nil, read: Set([objectType]), completion: { success, error in
            
        })
        
        // ヘルスケアデータの更新を検知するクエリ
        let query = HKObserverQuery(sampleType: objectType, predicate: nil, updateHandler: {
            query, completionHandler, error in
            if error != nil {
                return
            }
            // ヘルスケアデータの更新を検知したらローカル通知を送る
            let content = UNMutableNotificationContent()
            content.body = "体重データが更新されました"
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false)
            let request = UNNotificationRequest(identifier: "detection-test", content: content, trigger: trigger)
            UNUserNotificationCenter.current().add(request)
            completionHandler()
        })
        HKHealthStore().execute(query)
        
        // バックグランドでのヘルスケアデータの更新検知を有効にする
        HKHealthStore().enableBackgroundDelivery(for: objectType, frequency: .immediate, withCompletion: { success, error in
        })

        return true
    }
}
```

## 参考URL

- [HKObserverQuery](https://developer.apple.com/documentation/healthkit/hkobserverquery)
- [enableBackgroundDelivery(for:frequency:withCompletion:)](https://developer.apple.com/documentation/healthkit/hkhealthstore/1614175-enablebackgrounddelivery)
- [Healthkit background delivery when app is not running](https://stackoverflow.com/questions/26375767/healthkit-background-delivery-when-app-is-not-running)