進化したHealthKitを使って睡眠分析アプリを作ってみる

# はじめに
本記事はARISE analytics Advent Calendar 2022の13日目です。
昨日は@hktechさんの[AWS App Runnerでアプリケーションを動かす](https://qiita.com/hktech/items/c37dfcae8b17e5907264)
明日は@SaitomTechさんのServerless Framework を用いて Step Functions Distributed Map による Lambda の大規模並列処理を実現する
# HealthKitとは
HealthKitとは、iPhoneやApple Watch、他のヘルスケアアプリから集めた健康データを、iPhoneのヘルスケアアプリに連携する開発者向けツールです。例えば、Apple Watchで計測された心拍数や、体重計と連動しているアプリから連携される体重や体脂肪率をHealthKitが統合管理することで、我々開発者はさまざまなヘルスデータを利用することができます。
![HealthKit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949811/c1854e32-7061-4dec-caba-b5e3cebb43dc.png)
# HealthKitで扱えるデータ(iOS16)
HealthKitで扱えるデータの種類はiOSの進化と共に増えています。今回はiOS16から進化した睡眠について見ていきます。
- 栄養データ（39種）
- アクティブティ（15種）
- マインドフルネス（1種）
- **睡眠（1種）**
- 身体測定値（6種）
- リプロダクティブヘルス（6種）
- 心臓（8種）
- 検査結果（12種）
- バイタル（4種）
- ワークアウト（72種）

# 睡眠データについて
### 取得可能なデータについて
HealthKitでは睡眠分析用のデータ項目として、「**SleepAnalysis**」が用意されています。
SleepAnalysisでは、ユーザーの睡眠ステータスが変更された場合に以下のデータが書き込まれます。
SleepAnalysisはiOS15まで「寝ているか」 or 「起きているか」の情報しか取れませんでしたが、iOS16で詳細な睡眠ステータスが取得可能になりました。
![スクリーンショット 2022-12-11 17.29.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949811/8408f328-63eb-356d-0ffa-e29b917e5fe4.png)
参考：https://developer.apple.com/documentation/healthkit/hkcategoryvaluesleepanalysis
### データの書き込みタイミング
Apple Watchがユーザーの心拍数等の状態を監視し、睡眠ステータスが変更されたと判定されたタイミングでデータが書き込まれます。
![スクリーンショット 2022-12-11 17.30.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949811/60bdbe76-02f1-e89d-7c56-317c671d2001.png)
### 取得データのイメージ
書き込まれたデータは日付を指定してリストで取得可能です。
![スクリーンショット 2022-12-11 16.55.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949811/b775430c-3aaf-8213-ac16-fde7b6c62a9b.png)
### アプリ側で起床を検知することはできるのか？
睡眠分析機能を作ろうとしたときに、起床時に何らかの処理を行いたいケースがあると思います。
そこで、アプリ側でユーザーが起床したかどうかをリアルタイムで検知できるか調査しました。
結論から言うと、データの監視自体は可能でが、リアルタイムでの検知は難しいようです。
HealthKitのデータを監視するには**Observal機能**を使います。
Observal機能はアプリの状態（フォアグラウンド、バックグラウンド、キル）を問わず検知可能ですが、**iPhoneがロック状態では検知ができません。** 
![observal_image3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949811/2d39ac83-1a4f-ba71-a2fb-f0fe26e19e2b.png)
寝る時はiphoneをロックする人が大半だと思うので、起床後、初めてロック解除したタイミングで「X時に起床していた」という情報が取得できます。
参考：https://developer.apple.com/documentation/healthkit/hkobserverquery/executing_observer_queries


# サンプルコード
権限リクエスト
```swift
let healthStore = HKHealthStore()
    let readTypes = Set([
        HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!
    ])
     
    healthStore.requestAuthorization(toShare: [], read: readTypes, completion: { success, error in
        if success == false {
            print("データにアクセスできません")
            return
        }
    })
```
睡眠データをHealthKitから取得
```swift
func getSleepAnalysis()  {
        let query = HKSampleQuery(sampleType: HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!,
                                  predicate: HKQuery.predicateForSamples(withStart: Calendar.current.date(byAdding: .month, value: -1, to: Date())!, end: Date(), options: []),
                                  limit: HKObjectQueryNoLimit,
                                  sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: true)]){ (query, results, error) in
            
            guard error == nil else { print("error"); return }
            if let results = results as? [HKCategorySample] {
                let reversedResults = results.reversed()
                DispatchQueue.main.async {
                    reversedResults.map { data in
                        print(data.value.description)
                        // 睡眠データを使った処理を書く
                    }
                }
            }
        }
        healthStore.execute(query)
    }
```
睡眠データをObservalで監視
```swift
func setObserver() {
        let sampleType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!
        var observerQuery = HKObserverQuery(sampleType: sampleType, predicate: nil, updateHandler: { query, completionHandler, error in
            if error != nil { return }
            // 睡眠データの追加が検知された場合にここに来る
            
            // 直近一時間の睡眠データを取得
            let query = HKSampleQuery(sampleType: sampleType,
                                      predicate: HKQuery.predicateForSamples(withStart: Calendar.current.date(byAdding: .hour, value: -1, to: Date())!, end: Date(), options: []),
                                      limit: HKObjectQueryNoLimit,
                                      sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: true)]){ (query, results, error) in
                
                guard error == nil else {
                    completionHandler()
                    print("error");
                    return
                    
                }
            
                if let sample = results as? [HKCategorySample] {
                    // 追加された睡眠データを用いた処理を書く
                }
            }
        })
        
        healthStore.execute(observerQuery)
        
        // バックグランドでのヘルスケアデータの更新検知を有効にする
        healthStore.enableBackgroundDelivery(for: sampleType, frequency: .immediate) { success, error in
            if let error = error {
                print(error)
            }
            print(success)
        }
    }
```

# おわりに
HealthKitを扱うタイミングがあったので記事にしました。
HealthKitはiOSのアップデートと共にできることが増えており、睡眠データについてもiOS15までは「起きているか」 or 「寝ているか」の情報しか取得でいませんでした。しかし、iOS16のアップデートで睡眠の質についても分析することができるようになり、既存のヘルスヘアアプリに簡単に睡眠分析機能を追加可能になったと思います。
新しいApple Watchにも体温を扱う機能が追加され、今後もHealthKitのアップデートには注目したいですね。