カルボナーラ街道

計測と観察

HKSampleQueryをHKSampleQueryDescriptorに置き換える

開発環境

> xcodebuild -version
Xcode 13.3 Build version 13E113

モチベーション

HealthKitが提供するAPIにSwift Concurrency対応のものが増えていたので使ってみる。

HKSampleQueryDescriptor

iOS15.3以前では、HealthKitを使ってワークアウトを取得する時にHKSampleQueryを利用する必要があった。

import HealthKit

func getWorkouts(completion: @escaping (([HKWorkout]) -> Void)) {
    let query = HKSampleQuery(
        sampleType: .workoutType(),
        predicate: HKQuery.predicateForWorkouts(with: .cycling),
        limit: HKObjectQueryNoLimit,
        sortDescriptors: [
            NSSortDescriptor(
                key: HKSampleSortIdentifierStartDate,
                ascending: false
            )
        ]
    ) { _, samples, _ in
        guard let workouts = samples as? [HKWorkout] else { 
            return
        }
        completion(workouts)
    }
    healthStore.execute(query)
}


/// 呼び出し
getWorkouts { workouts in
    // workouts を使う処理
}

iOS15.4以降では、Swift Concurrency対応のHKSampleQueryDescriptorが利用できるようになった。

A query interface that reads samples using Swift concurrency.
https://developer.apple.com/documentation/healthkit/hksamplequerydescriptor

早速使ってみた。

コード

import HealthKit

func getWorkouts() async throws -> [HKWorkout] {
    let descriptor = HKSampleQueryDescriptor(
        predicates: [
            .sample(
                type: .workoutType(),
                predicate: HKQuery.predicateForWorkouts(with: .cycling)
            )
        ],
        sortDescriptors: [.init(\.startDate, order: .reverse)]
    )
    let results = try await descriptor.result(for: healthStore)
    return results as? [HKWorkout] ?? []
}


/// 呼び出し
let workouts = try! await getWorkouts()
// workouts を使う処理

悩ましい所

async throws キーワードが付与された関数/メソッドを呼び出す時に、呼び出し側でdo-catchを使ってエラーハンドリングができるが、ネストが深くなる問題は解決しない。 (throws も同様だけど)

do {
    let workouts = try await getWorkouts()
    // workouts を使う処理
} catch {
    // エラーハンドリング
}

対応として、事前に戻り値と同じ型の変数を定義しておけばネストが深くなる行数は抑えられそうだけど、初期値(いわゆるゼロ値)を与えるか、nil許容にする必要があって、甲乙つけがたい。

var workouts: [HKWorkout] = []
do {
    let _workouts = try await getWorkouts()
    workouts = _workouts
} catch {
    // エラーハンドリング
    // return させる
}
// workouts を使う処理

参考