開発環境
> 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 を使う処理