[iOS][HealthKit] HealthKit にアクセスする async/await 対応板

async/await 対応を活かした HealthKit へのアクセス方法を説明します。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterery beta 5
  • Xcode 13 beta5
  • iOS 15 beta

HealthKit のセットアップ

HealthKit の async/await 対応したセットアップは、以前説明しました。
[iOS][HealthKit] HealthKit のセットアップ async/await 対応板

HealthKit へのアクセス

セットアップができている前提で、HealthKit へのアクセス方法を説明していきます。

HealthKit への書き込み

HealthKit への書き込みは、save メソッドで行います。save メソッドは、async/await 対応されているメソッドです。


func save(_ object: HKObject) async throws

Apple のドキュメントは、こちら

ですので、作成した HKQuantitySample 等の保存する関数を async で作ると 以下のようになります。


func save(_ type: HKQuantityType, _ sample:HKQuantity) async -> Bool {
        // (1)
        guard healthStore.authorizationStatus(for: type) == .sharingAuthorized else { return false }

        do {
            // (2)
            try await healthStore.save(sample)
        } catch {
            logger.error("\(error.localizedDescription)")
            return false
        }
        return true
}
コード解説
  1. アクセス前に、authorizationStatus を確認します。必要な許諾がなければ終了です
  2. async 対応した save を呼び出します

この save メソッドも、async 指定されていますので、呼び出し側は、await を付与して呼び出す必要があります。

async メソッドの終了を待たない 呼び出し方

HealthKit への書き込みを待たずに別の処理に進みたい時には、以下のように呼び出します。


Task {
  _ = await save(type,sample)
}
processSomethingNext()

Task の中では、await 指定により save を待ちますが、processSomethingNext は、save 終了を待たずに実行されます。

async メソッドの終了を待つ呼び出し方

HealthKit への書き込みを待って 別処理を行いたいときは、以下のように呼び出す必要があります。


Task {
  _ = await save(type,sample)
  processSomethingNext()
}

await 指定で呼び出していますので、save 処理終了後に、processSomethingNext が呼び出されます。

HealthKit からの読み込み

HealthKit からデータを読み込むには、Query を使用する必要があります。

Query を使って、HealthKit からデータを取得する方法に async 対応されたものは用意されていません。

従来通りの Query を使ったデータ取得


func getHKDataFromHeatlKit() {
    let typeDescriptor = HKQueryDescriptor(sampleType: sometype, predicate: nil)
    let query = HKSampleQuery(queryDescriptors: [typeDescriptor], limit: Int(HKObjectQueryNoLimit)) { (query, samples, error) in
        if let error = error {
            print("\(error.localizedDescription)")
            return
        }
    
        if let samples = samples {
            // samples を保存
        }
        return
     }
     return
}
self.healthStore.execute(query)

いわゆる completionHandler を使った処理です。
Query に completion Handler をセットしておいて、HealthKit 経由で Query 実行したときに、completionHandler が呼ばれる形です。

async 対応した データ取得関数を作る

では 自分で async 対応したデータ取得関数を作ろうと考えると、取得してきたデータをどのように 関数返り値に反映させるかという点で困ってしまいます。

このような時に使用する概念が Continuation です。以下のように使用することで、completionHandler を使用した処理を async 対応に変換することができます。

以下では、エラー発生時には、空配列を返しています。エラー伝達が必要であれば、例外を使用します。


func hkData() async -> [HKQuantitySample] {
    return await withCheckedContinuation { continuation in
        let typeDescriptor = HKQueryDescriptor(sampleType: sometype, predicate: nil)
        let query = HKSampleQuery(queryDescriptors: [typeDescriptor], limit: Int(HKObjectQueryNoLimit)) { (query, samples, error) in
            if let error = error {
                print("\(error.localizedDescription)")
                continuation.resume(returning: [])
            } else if let samples = samples as? [HKQuantitySample] {
                continuation.resume(returning: samples)
            } else {
                continuation.resume(returning: [])
            }
        }
        self.healthStore.execute(query)
    }
}

# Continuation は、resume を 確実に1回だけ 呼び出す必要があるので、注意が必要です。

上記のコードを使うことで、以下のように、取得を待つようなコードを書くことが可能となります。


let retrievedData = await hkData()

以下のようにすることで、取得を待たないようなコードもかけます。


Task {
    let retrievedData = await hkData()
}
processSomethingElse()

まとめ:HealthKit にアクセスする async/await 対応板

HealthKit にアクセスする async/await 対応板
  • save は、async 対応しているものを使う
  • Query は、自分で async 対応させた関数を作る必要がある
  • completionHandler をつかう Query を async 対応させるには、Continuation を使用する

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です