← すべての記事

HealthKitワークアウトのライフサイクル:HKWorkoutSessionの状態とiOS 26のクロスプラットフォーム対応

HKWorkoutSessionはHealthKitのワークアウト状態マシンです。セッションは6つの状態(.notStarted.prepared.running.paused.stopped.ended)を遷移し、HKWorkoutSessionDelegateを通じてライフサイクルイベントを公開します。iOS 26以降は、Apple Watchに加えてiPhoneでも動作するようになりました1。セッションと組み合わせるHKLiveWorkoutBuilderは、サンプルとイベントを逐次的に収集します。iOS 26では同じビルダーAPIがiPhoneにも提供されました。本クラスタのwatchOSランタイム契約の記事では、watchOSアプリがバックグラウンドで動作し続けるには認識されたセッションタイプが必要だと論じました。HKWorkoutSessionはそうしたセッションタイプの1つであり、ライフサイクルの状態はランタイムモデルに直接対応しています。

本記事では、Appleの公式ドキュメントに沿ってワークアウトのライフサイクルを解説します。視点は「各状態で何が許可され、各遷移で何がトリガーされるか」です。ライフサイクルの管理を誤るワークアウトアプリは、データを失う(runningから早く遷移しすぎる)か、バッテリーを消耗させる(runningから遷移しない)かのどちらかになるからです。

TL;DR

  • HKWorkoutSessionのライフサイクルは、notStartedpreparedrunning →(オプションでpausedrunning)→ stoppedendedという流れです。遷移はHKWorkoutSessionDelegate.workoutSession(_:didChangeTo:from:date:)を通じて通知されます2
  • HKLiveWorkoutBuilderは、ワークアウトセッションと組み合わせて使うライブデータの蓄積機構です。2018年にwatchOSで登場し、iOS 26以降、iPadOS 26以降、Mac Catalyst 26以降にも展開されました。iPhoneのワークアウトはApple Watchと同じHKLiveWorkoutBuilder APIを使いますが、ランタイムモデルとセンサーの利用可否にプラットフォーム固有の違いがあります3
  • セッションのprepare()メソッドは、startActivity(_:)が実際のワークアウトを開始する前にセンサーをウォームアップします。Appleの推奨は、prepare()startActivity(_:)の間に3秒のカウントダウンUIを挟み、心拍センサーや外部Bluetoothデバイスが接続する時間を確保することです。
  • stopped状態は一時的なものです。アプリはこの状態でメトリクスを確定できますが、セッションを再開することはできません。end()を呼ぶとendedへ遷移し、これは終端状態となります。
  • 本クラスタのwatchOSランタイム契約の記事では、ワークアウトセッションがリストドロップを跨いでwatchOSアプリを動作させ続ける仕組みを扱っています。ライフサイクルの状態マシンとランタイム維持の契約は、同じ表面の両面なのです。

6つの状態

HKWorkoutSessionStateはライフサイクルを列挙します2

.notStarted セッションは作成されましたが、まだprepareされていません。センサーはウォームアップされておらず、アプリはアクティブなワークアウトのホストとはまだ見なされません。.preparedへの遷移は、アプリがprepare()を呼び出したときに発生します。

.prepared セッションがprepare()を呼んだ状態です。センサーがウォームアップ中ですが、ワークアウトはまだ始まっていません。心拍モニタが接続し、モーションセンサーが初期化され、GPSが測位を取得します。ユーザー向けのパターンは3秒のカウントダウン(「準備して… 3、2、1、スタート!」)です。このウィンドウの間にシステムはクリーンな信号を取得する時間を得るため、running状態の最初のメトリクスが正確になります。

.running アクティブなワークアウト状態です。アプリはメトリクスを収集し、ライブデータを表示し、(watchOSでは)workout-activeランタイム契約を通じて画面をオンに保ちます。.runningへの遷移はstartActivity(_:)を通じて発生します。

.paused ユーザーが一時停止した状態です。アプリはアクティブなメトリクス(距離など)を収集しなくなりますが、セッションは保持されます。resume()を呼ぶと.runningに戻ります。1つのセッション内で、一時停止と再開のサイクルは何度でも発生し得ます。

.stopped ワークアウト後の一時的な状態です。セッションはアクティブフェーズを終了しましたが、まだ確定されていません。ライブビルダーはまだメトリクスを確定できます。.stoppedからend()を呼ぶと.endedへ遷移します。.stoppedからセッションを再開することはできません。

.ended 終端状態です。セッションは完了し、ライブビルダーには確定の指示が出されています。アプリがビルダーでfinishWorkout(completion:)を呼んでいれば、ワークアウトはHealthKitに保存されます。.endedに入ると、セッションは操作できなくなります。

状態図には1つ特有の落とし穴があります。.stoppedから.runningに戻るパスは存在しません。ユーザーが「終了を取り消したい」ワークアウトについては、古いセッションを再開するのではなく、新しいセッションを開始する必要があります。

遷移を駆動するメソッド

HKWorkoutSessionは状態遷移のために以下のメソッドを公開します1

  • prepare().notStartedから.preparedに遷移します。センサーをウォームアップします。
  • startActivity(with: Date).preparedから.runningに遷移します。Dateパラメータでアプリは公式の開始時刻を設定できます(通常は.now)。
  • pause().runningから.pausedに遷移します。
  • resume().pausedから.runningに戻ります。
  • stopActivity(with: Date).running(または.paused)から.stoppedに遷移します。Dateは公式の終了時刻です。
  • end().stoppedから.endedに遷移します。

prepare()からstartActivity(_:)までのパターンはウォームアップウィンドウです。stopActivity(_:)からend()までのパターンはクリーンアップウィンドウとなります。セッションが終了する前に、ライブビルダーが最後のサンプルを追加する機会を得るのです。

watchOSとiPhoneでのHKLiveWorkoutBuilder

HKLiveWorkoutBuilderはセッションと組み合わせて使うライブデータ蓄積機構です3。ビルダーはwatchOS 5でwatchOSに登場し、iOS 26以降、iPadOS 26以降、Mac Catalyst 26以降に拡張されました。ビルダーのライフサイクルはセッションのものと連動します。

let configuration = HKWorkoutConfiguration()
configuration.activityType = .running
configuration.locationType = .outdoor

let session = try HKWorkoutSession(healthStore: store, configuration: configuration)
let builder = session.associatedWorkoutBuilder()
builder.dataSource = HKLiveWorkoutDataSource(healthStore: store, workoutConfiguration: configuration)

session.delegate = self
builder.delegate = self

session.prepare()
// User taps Start after countdown
session.startActivity(with: Date())
try await builder.beginCollection(at: Date())

// During the workout, metrics flow into the builder via the data source.
// builder.collectedTypes contains the sample types being collected.
// builder.statistics(for:) returns running stats.

// User ends the workout
session.stopActivity(with: Date())
try await builder.endCollection(at: Date())
let workout = try await builder.finishWorkout()
session.end()

ワークアウトをまとめあげる要素は3つあります。

  • HKWorkoutConfigurationはアクティビティタイプと場所を指定します。アクティビティタイプはメトリクス選択を決めます(ランニングのワークアウトはペースを収集しますが、室内サイクリングのワークアウトはペースを収集しません)。
  • HKLiveWorkoutDataSourceはセッションのセンサー設定からビルダーのデータ蓄積へと繋ぐ橋渡しです。データソースがサンプルを発行し、ビルダーがそれを受け取って保存します。
  • HKLiveWorkoutBuilderは進行中のワークアウトの状態を保持し、保存されるHKWorkoutオブジェクトを最終化します。

パターンは逐次的です。.running状態の間、サンプルは継続的に流入し、ビルダーは実行中の統計に統合します。最後のfinishWorkout()で完全なワークアウトがHealthKitに書き込まれます。

iOS 26:iPhoneでのワークアウト

iOS 26では、watchOSが使うのと同じHKLiveWorkoutBuilderおよびデータソースAPIとともに、HKWorkoutSessionがiPhoneに導入されました4。構築方法は同じで、プラットフォーム固有の違いはAPIの表面ではなく、ランタイムモデル、センサーの利用可否、プライバシーの扱いにあります。

iOS 26のワークアウトAPIが可能にするユースケースは次のとおりです。 - 電話をコンパニオンとして使うワークアウトアプリ(iPhoneがWatchセッションと並行して心拍モニターのデータを保持)。 - iPhone単独のフィットネスアプリ:Apple Watchを持っていないユーザー向けに、iPhoneが内蔵センサーや接続されたアクセサリを通じてセッションを追跡します。 - デバイス間のセッション継続:Apple WatchのセッションをiPhoneに引き継ぐ(ユーザーがWatchを外したがiPhoneでの追跡は続けたい場合)、あるいはその逆。

挙げておくべきプラットフォームの違いは次のとおりです。 - センサーの利用可否。 iPhoneは加速度計とGPSを備えますが、内蔵の心拍センサーはありません。iOSのワークアウトで心拍が必要なアプリは、Bluetoothの心拍ストラップとペアリングするか、HealthKitを介して接続済みのApple Watchから読み取ります。 - ランタイムモデル。 Apple Watchのworkout-activeランタイムは、リストドロップ中も連続的なセンサーアクセスを保証します。iPhoneのランタイムは、システム通常のフォアグラウンド/バックグラウンドのライフサイクルに加え、シーンデリゲートを介したクラッシュ復旧(WWDC 2025のセッション322で取り上げられています)に依拠しており、これは異なる形の保証です。 - プライバシーとロック画面の動作。 デバイスがロックされている間に動作するiPhoneのワークアウトでは、サンプル収集を継続するために明示的な設定が必要です。ロック画面はリストドロップよりも強いプライバシー境界だからです。

デリゲートプロトコル

HKWorkoutSessionDelegateは状態遷移とエラーを通知します5

extension WorkoutCoordinator: HKWorkoutSessionDelegate {
    func workoutSession(
        _ workoutSession: HKWorkoutSession,
        didChangeTo toState: HKWorkoutSessionState,
        from fromState: HKWorkoutSessionState,
        date: Date
    ) {
        switch toState {
        case .running:
            // workout is active
        case .paused:
            // user paused
        case .stopped:
            // finalize metrics
        case .ended:
            // workout done; cleanup
        default:
            break
        }
    }

    func workoutSession(
        _ workoutSession: HKWorkoutSession,
        didFailWithError error: Error
    ) {
        // session failed (e.g., heart rate sensor disconnected unexpectedly)
    }
}

デリゲートは状態遷移の唯一の真実の源です。メソッド呼び出しから状態を推測するアプリ(「startActivity()を呼んだから今はrunningのはず」)は、システム側が適用する状態変化(ユーザーが静止しているときの自動一時停止、Watchが外されたときの自動終了)を取りこぼします。デリゲート駆動のパターンが正解です。

ランタイム契約

HKWorkoutSessionは、マインドフルネス、アラーム、オーディオ録音セッションと並んで、watchOSがアプリをバックグラウンドで動かし続けるために認識するセッションタイプの1つです6。契約はこうです。セッションが.prepared.running.paused.stoppedのいずれかにある間、アプリは動き続け、ユーザーが手首を上げると画面が起動し、センサーは継続的にアプリへストリームします。

本クラスタのwatchOSランタイム契約の記事で詳しく扱っています。ワークアウトアプリにとっての要点は、ライフサイクルの状態マシンこそがwatchOSに「このアプリを動かし続けて」と伝えるものであり、.endedに遷移すると契約は解放されてOSがアプリを停止できるようになる、ということです。

実用上の含意:ワークアウトセッションを早めに終了してはいけません。電話のためにユーザーが一旦ワークアウトから離れて戻ってきた場合、セッションは.runningのまま(またはpause()で一時停止)にしておくべきで、終了させてはいけません。終了して再開すると、データもランタイムの連続性も失われます。

よくある失敗

ワークアウトアプリの障害ログから抽出した3つのパターンを示します。

prepare()をスキップする。 prepare()を呼ばずにstartActivity(_:)を呼ぶアプリでは、ワークアウト最初の5~10秒の心拍データが信頼できなかったり(センサーがウォームアップされていない)、欠けていたり(Bluetoothの心拍ストラップが接続されていない)します。修正方法は、必ずprepare()を呼び、短いカウントダウンUIを表示してからstartActivity(_:)を呼ぶことです。

.runningから直接end()を呼ぶ。 .stoppedをスキップすると、メトリクス確定のウィンドウもスキップされてしまいます。セッションが終了する前にライブビルダーが最終サンプルを処理しきれず、サマリ統計が欠落することがあります。修正方法は、まずstopActivity(_:)を呼び、デリゲートのコールバックで.stoppedを確認したうえでend()を呼ぶことです。

デリゲートを使わずに状態を推測する。 ローカル状態(isWorkoutActive: Bool)を追跡するだけでデリゲートを接続しないアプリは、システム駆動の遷移(自動一時停止、Watch取り外し時の自動終了、エラー状態)を取りこぼします。修正方法は、必ずデリゲートを真実の源として使うことです。

このパターンがiOS 26以降のアプリにとって意味するもの

要点は3つです。

  1. ライフサイクルをUIの状態に明示的に対応づける。 ワークアウトアプリのUIには明らかな状態があります。未開始、準備中、アクティブ、一時停止、サマリ、完了です。それぞれをHKWorkoutSessionStateに対応づけてください。場当たり的なbooleanでUIを動かさず、デリゲートを通じてセッションが報告する状態にバインドしましょう。

  2. メトリクスを表示するセッションには、必ずprepare()とカウントダウンUIを使う。 3秒のウォームアップは、ユーザーが信頼するデータと割り引いて見るデータの分かれ目になります。コストは小さなUI要素で、得られるのは信頼できるメトリクスです。

  3. iOS 26のiPhoneワークアウトセッションには、異なるビルダーコードが必要。 セッションのAPIは共通ですが、ビルダー側はプラットフォーム固有です。iOSとwatchOSでコードパスを共有するアプリには、明示的な#if os(watchOS)分岐か、その差を吸収するラッパーが必要です。

Apple Ecosystemクラスタの全記事:型付きApp IntentsMCPサーバールーティングの問題Foundation ModelsランタイムとツーリングLLMの違い3つの表面単一の真実の源パターン2つのMCPサーバーApple開発のためのフックLive ActivitieswatchOSランタイムSwiftUI内部RealityKitの空間メンタルモデルSwiftDataのスキーマ規律Liquid Glassパターンマルチプラットフォーム出荷プラットフォームマトリクスVisionフレームワークSymbol EffectsCore MLによる推論Writing Tools APISwift TestingプライバシーマニフェストプラットフォームとしてのアクセシビリティSF ProのタイポグラフィvisionOSの空間パターンSpeechフレームワークSwiftDataマイグレーションtvOSのフォーカスエンジン@Observable内部SwiftUI LayoutプロトコルカスタムSF SymbolsAVFoundation HDR書かないと決めていること。ハブはApple Ecosystemシリーズにあります。AIエージェントを使ったiOS開発の文脈はiOS Agent Developmentガイドを参照してください。

FAQ

iPhoneはApple Watchと同じHKLiveWorkoutBuilderを使うのですか?

はい、iOS 26以降は同じです。同じHKLiveWorkoutBuilder APIがiOS 26以降、iPadOS 26以降、Mac Catalyst 26以降、watchOS 5以降で提供されます。プラットフォームの違いはランタイムモデルとセンサーの利用可否にあり、ビルダーAPIにはありません。iPhoneのワークアウトは、シーンデリゲートを介してロック画面のプライバシーとクラッシュ復旧を扱います(WWDC 2025のセッション322を参照)。これはwatchOSのリストドロップ・ランタイム保証とは異なりますが、データ蓄積APIは同じです。

ワークアウトの最大時間はどれくらいですか?

時間の上限はありません。実用的な制限はバッテリー(Apple Watchは連続ワークアウトで約6~8時間持続)とストレージ(高頻度データのワークアウトはすぐに蓄積する)から来ます。マラソン用のランニングアプリ(12時間以上のワークアウト)は今日すでに出荷されており、フレームワークはそれをサポートします。

自動一時停止はどう扱えばよいですか?

HKWorkoutConfiguration.activityTypeを自動一時停止に対応するもの(.runningなど)に設定してください。watchOSがユーザーの動きに基づいて自動的に一時停止と再開を行います。状態遷移はデリゲートを通じて流れるので、ユーザー操作による一時停止と同様に扱ってください。

ワークアウト中にユーザーがWatchを外したらどうなりますか?

セッションは現在の状態(通常は.running)のまま継続します。Watchが手首から長時間離れていると、watchOSは最終的にセッションを終了させます。これが起きるとデリゲートのdidFailWithErrorコールバックが発火します。デバイス間セッション(iOS 26以降)に対応したアプリは、ユーザーが両方のデバイスを持っていればiPhoneにハンドオフできます。

ワークアウトはHealthKitに保存すべきですか?

ほぼ常に保存すべきです。builder.finishWorkout(completion:)を呼ぶと完全なワークアウトがHealthKitに書き込まれ、データはアクティビティアプリ、ヘルスケアアプリのワークアウトリスト、そしてユーザーが認可した他のアプリにも表示されます。保存をスキップするとデータは破棄され、フレームワークには復旧手段はありません。

これは最近のApple Healthへの追加機能とどう関係していますか?

iOS 26 / watchOS 26はワークアウトAPIを2つの具体的な方向で拡張しました。1つ目はHKWorkoutSessionをiPhoneにもたらしたこと(上述)、2つ目はアクティビティタイプ一覧と自動検出のカバー範囲を広げたことです。本クラスタのwatchOSランタイム契約の記事はランタイム面を扱い、本記事はライフサイクル面を扱います。両者を合わせることで、ワークアウトアプリを出荷するための表面の全体像が描けます。

参照


  1. Apple Developer Documentation:HKWorkoutSession。状態遷移、設定、デリゲートプロトコルを備えるセッションクラス。 

  2. Apple Developer Documentation:HKWorkoutSessionState。5つの状態ケース(.notStarted.prepared.running.paused.stopped.ended)とその意味論。 

  3. Apple Developer Documentation:HKLiveWorkoutBuilderおよびHKLiveWorkoutDataSource。ライブビルダーAPI(watchOS 5以降、iOS 26以降、iPadOS 26以降、Mac Catalyst 26以降)とそのデータソース。 

  4. Apple Developer:Track workouts with HealthKit on iOS and iPadOS(WWDC 2025セッション322)。HKWorkoutSessionのiPhoneへのiOS 26拡張。 

  5. Apple Developer Documentation:HKWorkoutSessionDelegate。状態遷移とエラーコールバックを備えたデリゲートプロトコル。 

  6. Apple Developer Documentation:Background Execution on watchOS。リストドロップ中もアプリを動かし続けるセッションタイプを記述する、watchOSのランタイム契約。 

関連記事

iOS 26のHealthKit + SwiftUI:認可、サンプルタイプ、そして2つの実アプリから得たクロスプラットフォームパターン

Water(水分摂取トラッキング、HKQuantitySample)とReturn(マインドフルセッション、HKCategorySample)から得た本番運用パターン。許可UX、async ラッパー、watchOSバリアント、そして避けるべ…

4 分で読める

watchOSのランタイムは契約であり、バックグラウンドタスクではない

watchOSにはiOSのようなバックグラウンドはありません。WKExtendedRuntimeSessionが契約であり、これがなければ手首を下ろした瞬間にアプリは停止します。Returnで実装したパターンを紹介します。

2 分で読める

ループ・エンジニアリング――検証が安いところでループは勝つ

ループ・エンジニアリングを、Boris Cherny の全文書き起こしと照合する。彼が名前を挙げるループはどれも検証が安い。その制約こそが、何を自動化すべきかを決める。

4 分で読める