Open23

【HealthKit】Workout関連

muranakarmuranakar

HKWorkoutSessionは、運動時のモニタリングをしたいときに使用できるセッションである。
HKWorkoutSessionを使用して、Apple Watchでユーザーのアクティビティをトラッキングします。セッションが実行されている間、システムは指定されたアクティビティ用にウォッチのセンサーを調整する。

muranakarmuranakar

Apple Watchでワークアウトをトラッキングするには、ワークアウトセッションを設定、開始、保存する必要があります。

muranakarmuranakar

Workoutのデータ取得・書き込みに必要なデータ型を指定して、権限をリクエストする必要がある。

// The quantity type to write to the health store.
let typesToShare: Set = [
    HKQuantityType.workoutType()
]


// The quantity types to read from the health store.
let typesToRead: Set = [
    HKQuantityType.quantityType(forIdentifier: .heartRate)!,
    HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!,
    HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)!
]


// Request authorization for those quantity types.
healthStore.requestAuthorization(toShare: typesToShare, read: typesToRead) { (success, error) in
    // Handle errors here.
}
muranakarmuranakar

WorkoutSessionがアクティブなアプリはバックグラウンドで実行できるため、WatchKit App Extensionにバックグラウンドモード機能を追加する必要がある。
WorkoutSessionには、ワークアウト処理のバックグラウンドモードを設定する必要がある。

muranakarmuranakar

音楽をバックグラウンドで再生する場合もチェックする必要がある。

muranakarmuranakar

セッションが実行されている間、Apple Watchは活動のエネルギー消費サンプルを自動的にHealthKitストアに保存します。HealthKitは、ランニング、ウォーキング、サイクリング、階段登りの活動に対して最適化されたカロリー計算を提供します。さらに、ランニング、ウォーキング、サイクリングの活動に関しては、屋内と屋外の場所によって計算が異なります。

muranakarmuranakar

HKWorkoutConfiguration() を使用して、設定を使用してワークアウト・セッションを作成し、セッションからHKLiveWorkoutBuilderを取得します。

do {
    session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
    builder = session.associatedWorkoutBuilder()
} catch {
    // Handle failure here.
    return
}

HKWorkoutSessionは例外処理を投げる可能性があるため、do catchが必要。

muranakarmuranakar

HKLiveWorkoutDataSourceオブジェクトを作成し、workout builderに割り当てます。

builder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,
                                             workoutConfiguration: configuration)
muranakarmuranakar

ワークアウトセッションとライブデータソースに同じ設定オブジェクトを使用します。セッションが実行されている間、Apple Watchは自動的にワークアウトに関するデータを収集し、サンプルをHealthKitに保存します。例えば、屋外ランニングセッションは、activeEnergyBurned、basalEnergyBurned、heartRate、distanceWalkingRunningのサンプルを収集し、保存します。

muranakarmuranakar

sessionとbuilderのデリゲートを設定することが可能です。

session.delegate = self
builder.delegate = self
muranakarmuranakar

HKWorkoutSessionDelegateは、セッションの状態が変化したとき、イベントが発生したとき、またはセッションがエラーで失敗したときに更新を受け取ります。

https://developer.apple.com/documentation/healthkit/hkworkoutsessiondelegate

HKLiveWorkoutBuilderDelegateは、Apple Watchまたはアプリがビルダーに新しいサンプルやイベントを追加したときに更新を受け取ります。

https://developer.apple.com/documentation/healthkit/hkliveworkoutbuilderdelegate

muranakarmuranakar

セッションとビルダーの違い

両者の違いがイメージわかなかったので、まとめてみました。

セッション(Session):

  • ワークアウト全体の期間と状態を管理。
  • ワークアウトの開始、停止、一時停止、再開を制御。
  • イベントやエラーの通知を担当。

ビルダー(Builder):

  • セッション中に収集されるデータの記録と管理。
  • リアルタイムでのデータサンプルとイベントの収集。
muranakarmuranakar

デフォルトでは、ワークアウトセッションは自動的にすべてのイベントをビルダーに転送するので、両方のデリゲートが同じイベントセットを受け取るはずです。ただし、ビルダーに設定されるイベントを制御したい場合は、ビルダーのshouldCollectWorkoutEventsプロパティをfalseに設定できます。

「ビルダーに設定されるイベントを制御したい場合は」はどのような状況なのだろうか。

muranakarmuranakar

セッションとビルダーを開始します。

session.startActivity(with: Date())
builder.beginCollection(withStart: Date()) { (success, error) in
   
   guard success else {
       // Handle errors.
   }
   
   // Indicate that the session has started.
}
muranakarmuranakar

セッションが実行されている間、Apple Watchはワークアウトの設定に基づいてサンプルとイベントを自動的に収集し、追加します。

muranakarmuranakar

ビルダーが新しいサンプルまたはイベントを受信するたびに、アプリのユーザー インターフェイスを更新する必要があります。新しいサンプルを使用するには、HKLiveWorkoutBuilderDelegateworkoutBuilder(_:didCollectDataOf:)メソッドを実装します。

func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
    for type in collectedTypes {
        guard let quantityType = type as? HKQuantityType else {
            return // Nothing to do.
        }
        
        // Calculate statistics for the type.
        let statistics = workoutBuilder.statistics(for: quantityType)
        let label = labelForQuantityType(quantityType)
        
        DispatchQueue.main.async() {
            // Update the user interface.
        }   
    }
}
muranakarmuranakar

新しいイベントに応答するには、HKLiveWorkoutBuilderDelegateworkoutBuilderDidCollectEvent(_:)メソッドを実装します。

func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
    
    let lastEvent = workoutBuilder.workoutEvents.last
    
    DispatchQueue.main.async() {
        // Update the user interface here.
    }    
}

HKWorkoutEventType.lapイベントが発生したら、表示されるラップ数を増やすことができます。

muranakarmuranakar

バックグラウンドでの実行

  • ワークアウトセッションはバックグラウンドで実行され続ける。
  • バックグラウンドで音声や触覚フィードバックを提供できる。
  • 過剰なCPU使用の場合、watchOSはアプリを一時停止することがある。
  • XcodeやInstrumentsを使ってCPU使用量をテストする必要がある。
muranakarmuranakar

ユーザがワークアウトを終了したら、セッションを終了し、ビルダの endCollection(withEnd:completion:) メソッドと finishWorkout(completion:) メソッドを呼び出します。

session.end()
builder.endCollection(withEnd: Date()) { (success, error) in
    
    guard success else {
        // Handle errors.
    }
    
    builder.finishWorkout { (workout, error) in
        
        guard workout != nil else {
            // Handle errors.
        }
        
        DispatchQueue.main.async() {
            // Update the user interface.
        }
    }
}
muranakarmuranakar

クラッシュからの回復
ワークアウトセッション中にアプリがクラッシュした場合、システムはアプリの再起動時にhandleActiveWorkoutRecovery()メソッドを呼び出します。このメソッドの実装では、HealthKitストアのrecoverActiveWorkoutSession(completion:)メソッドを呼び出します。HealthKitは以前のワークアウトセッションの復元を試み、新しいセッションオブジェクトかエラーをcompletionブロックに返します。