← Tous les articles

Cycle de vie des entraînements HealthKit : les états de HKWorkoutSession et la surface multiplateforme d'iOS 26

HKWorkoutSession est la machine à états des entraînements de HealthKit. La session traverse six états (.notStarted, .prepared, .running, .paused, .stopped, .ended), expose les événements du cycle de vie via HKWorkoutSessionDelegate et (depuis iOS 26) s’exécute sur iPhone en plus de l’Apple Watch1. Un HKLiveWorkoutBuilder associé à la session collecte les échantillons et les événements de manière incrémentale ; iOS 26 a apporté le même builder API sur iPhone. L’article watchOS Runtime Contract du cluster soutenait que les applications watchOS ont besoin d’un type de session reconnu pour continuer à s’exécuter en arrière-plan ; HKWorkoutSession est l’un de ces types de session, et les états du cycle de vie correspondent directement au modèle d’exécution.

L’article parcourt le cycle de vie des entraînements en s’appuyant sur la documentation d’Apple. Le cadre est « ce que chaque état permet et ce que chaque transition déclenche », car les applications d’entraînement qui gèrent mal le cycle de vie perdent soit des données (transition hors de running trop tôt), soit vident la batterie (jamais de transition hors de running).

TL;DR

  • Cycle de vie de HKWorkoutSession : notStartedpreparedrunning → (optionnel pausedrunning) → stoppedended. Les transitions sont signalées via HKWorkoutSessionDelegate.workoutSession(_:didChangeTo:from:date:)2.
  • HKLiveWorkoutBuilder est l’accumulateur de données en direct associé à la session d’entraînement. Il est apparu sur watchOS en 2018 et a été livré sur iOS 26+, iPadOS 26+ et Mac Catalyst 26+. Les entraînements sur iPhone utilisent le même API HKLiveWorkoutBuilder que l’Apple Watch, avec des différences spécifiques à la plateforme dans le modèle d’exécution et la disponibilité des capteurs3.
  • La méthode prepare() de la session préchauffe les capteurs avant que startActivity(_:) ne lance l’entraînement réel. La recommandation d’Apple est d’afficher un compte à rebours de 3 secondes entre prepare() et startActivity(_:) pour laisser aux capteurs de fréquence cardiaque et aux appareils Bluetooth externes le temps de se connecter.
  • L’état stopped est transitoire : les applications peuvent finaliser les métriques dans cet état mais ne peuvent pas reprendre la session. Appeler end() fait passer à ended, qui est terminal.
  • L’article watchOS Runtime Contract du cluster explique comment la session d’entraînement maintient l’application watchOS en cours d’exécution lors du baissé de poignet. La machine à états du cycle de vie et le contrat de maintien d’exécution sont les deux moitiés de la même surface.

Les six états

HKWorkoutSessionState énumère le cycle de vie2 :

.notStarted. La session a été créée mais pas préparée. Les capteurs ne sont pas préchauffés ; l’application n’est pas encore considérée comme un hôte d’entraînement actif. La transition vers .prepared se produit lorsque l’application appelle prepare().

.prepared. La session a appelé prepare() ; les capteurs se préchauffent mais l’entraînement n’a pas commencé. Les moniteurs de fréquence cardiaque se connectent, les capteurs de mouvement s’initialisent, le GPS obtient une position. Le motif côté utilisateur est un compte à rebours de 3 secondes (« Préparez-vous… 3, 2, 1, GO ! ») ; pendant cette fenêtre, le système a le temps d’acquérir un signal propre afin que les premières métriques de l’état running soient précises.

.running. L’état d’entraînement actif. L’application collecte les métriques, affiche les données en direct et (sur watchOS) maintient l’écran allumé via le contrat d’exécution actif d’entraînement. La transition vers .running se produit via startActivity(_:).

.paused. Un état de pause utilisateur. L’application ne collecte plus de métriques actives (par exemple, la distance) mais la session est préservée ; appeler resume() revient à .running. Le cycle pause/reprise peut se produire un nombre illimité de fois au sein d’une même session.

.stopped. Un état post-entraînement transitoire. La session a terminé sa phase active mais n’a pas été finalisée ; le builder en direct peut encore finaliser les métriques. Depuis .stopped, appeler end() fait passer à .ended. L’application ne peut pas reprendre depuis .stopped.

.ended. L’état terminal. La session est terminée ; le builder en direct a reçu l’instruction de finaliser ; l’entraînement est enregistré dans HealthKit (si l’application a appelé finishWorkout(completion:) sur le builder). Une fois dans .ended, la session n’est plus manipulable.

Le diagramme d’états comporte un piège spécifique : il n’existe aucun chemin de .stopped vers .running. Un entraînement que l’utilisateur souhaite « désannuler » nécessite de démarrer une nouvelle session, et non de reprendre l’ancienne.

Les méthodes qui pilotent les transitions

HKWorkoutSession expose les méthodes suivantes pour les transitions d’états1 :

  • prepare(). Transition de .notStarted à .prepared. Préchauffe les capteurs.
  • startActivity(with: Date). Transition de .prepared à .running. Le paramètre Date permet à l’application de définir l’heure de début officielle (typiquement .now).
  • pause(). Transition de .running à .paused.
  • resume(). Transition de .paused vers .running.
  • stopActivity(with: Date). Transition de .running (ou .paused) à .stopped. La Date est l’heure de fin officielle.
  • end(). Transition de .stopped à .ended.

Le motif allant de prepare() à startActivity(_:) est la fenêtre de préchauffage. Le motif allant de stopActivity(_:) à end() est la fenêtre de nettoyage : le builder en direct a la possibilité d’ajouter les derniers échantillons avant que la session ne se termine.

HKLiveWorkoutBuilder sur watchOS et iPhone

HKLiveWorkoutBuilder est l’accumulateur de données en direct associé à la session3. Le builder a été livré sur watchOS dans watchOS 5 et a été étendu à iOS 26+, iPadOS 26+ et Mac Catalyst 26+. Le cycle de vie du builder s’apparie avec celui de la session :

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()

Trois éléments assemblent l’entraînement :

  • HKWorkoutConfiguration spécifie le type d’activité et l’emplacement. Le type d’activité guide la sélection des métriques (un entraînement de course collecte l’allure, un entraînement de cyclisme en intérieur ne le fait pas).
  • HKLiveWorkoutDataSource est le pont entre la configuration des capteurs de la session et l’accumulation des données par le builder. La source de données publie les échantillons ; le builder les reçoit et les stocke.
  • HKLiveWorkoutBuilder détient l’état de l’entraînement en cours et finalise l’objet HKWorkout enregistré.

Le motif est incrémental : les échantillons s’écoulent en continu pendant l’état .running ; le builder les intègre dans des statistiques courantes ; l’appel final à finishWorkout() écrit l’entraînement complet dans HealthKit.

iOS 26 : les entraînements sur iPhone

iOS 26 a apporté HKWorkoutSession sur iPhone avec les mêmes API HKLiveWorkoutBuilder et source de données que watchOS utilise4. La construction est identique ; les différences spécifiques à la plateforme se trouvent dans le modèle d’exécution, la disponibilité des capteurs et la gestion de la confidentialité plutôt que dans la surface API.

Les cas d’usage que la API d’entraînement d’iOS 26 permet : - Applications d’entraînement iPhone-comme-compagnon (l’iPhone détient les données du moniteur de fréquence cardiaque parallèlement à la session Watch). - Applications de fitness uniquement pour iPhone pour les utilisateurs sans Apple Watch, où l’iPhone suit la session via les capteurs intégrés et les accessoires connectés. - Continuité de session entre appareils : une session Apple Watch qui passe la main à l’iPhone (l’utilisateur retire la Watch mais souhaite que l’iPhone continue le suivi) ou vice versa.

Les différences de plateforme à mentionner : - Disponibilité des capteurs. L’iPhone dispose d’un accéléromètre et d’un GPS mais pas de capteur de fréquence cardiaque intégré. Les applications qui ont besoin de la fréquence cardiaque sur les entraînements iOS s’apparient avec une ceinture Bluetooth de fréquence cardiaque ou lisent depuis une Apple Watch connectée via HealthKit. - Modèle d’exécution. L’exécution active d’entraînement de l’Apple Watch garantit un accès continu aux capteurs lors du baissé de poignet. L’exécution sur iPhone repose sur le cycle de vie normal premier plan/arrière-plan du système plus la récupération après crash via le scene delegate (couvert dans la session 322 de la WWDC 2025), ce qui constitue une forme de garantie différente. - Confidentialité et comportement de l’écran de verrouillage. Les entraînements iPhone qui s’exécutent pendant que l’appareil est verrouillé nécessitent une configuration explicite pour continuer à collecter des échantillons, car l’écran de verrouillage est une frontière de confidentialité plus forte que le baissé de poignet.

Le protocole delegate

HKWorkoutSessionDelegate rapporte les transitions d’états et les erreurs5 :

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)
    }
}

Le delegate est l’unique source de vérité pour les transitions d’états. Les applications qui infèrent l’état à partir des appels de méthodes (« j’ai appelé startActivity(), donc nous sommes maintenant en cours d’exécution ») manquent les changements d’état que le système applique (mise en pause automatique lorsque l’utilisateur est immobile, fin automatique lorsque la montre est retirée). Le motif piloté par le delegate est le bon.

Le contrat d’exécution

HKWorkoutSession est l’un des types de session que watchOS reconnaît pour maintenir une application en cours d’exécution en arrière-plan, aux côtés des sessions de pleine conscience, d’alarme et d’enregistrement audio6. Le contrat : tant que la session est dans l’état .prepared, .running, .paused ou .stopped, l’application continue de s’exécuter ; l’écran s’allume lorsque l’utilisateur lève le poignet ; les capteurs envoient un flux continu vers l’application.

L’article watchOS Runtime Contract du cluster couvre cela en détail. Le point pertinent pour les applications d’entraînement : la machine à états du cycle de vie est ce qui dit à watchOS « garde cette application en cours d’exécution » ; passer à .ended libère le contrat et permet à l’OS de suspendre l’application.

Une implication pratique : ne mettez pas fin prématurément à une session d’entraînement. Si l’utilisateur s’éloigne de son entraînement pour un appel téléphonique et revient, la session devrait rester en .running (ou être mise en pause via pause()), et non être terminée. La terminer puis la redémarrer fait perdre les données et la continuité d’exécution.

Échecs courants

Trois motifs issus des journaux d’échecs d’applications d’entraînement :

Sauter prepare(). Les applications qui appellent startActivity(_:) sans appeler d’abord prepare() produisent des entraînements où les 5 à 10 premières secondes de données de fréquence cardiaque sont peu fiables (le capteur n’a pas été préchauffé) ou manquantes (la ceinture Bluetooth de fréquence cardiaque ne s’était pas connectée). Correction : toujours appeler prepare(), afficher une brève interface de compte à rebours, puis startActivity(_:).

Appeler end() directement depuis .running. Sauter .stopped saute la fenêtre de finalisation des métriques. Le builder en direct peut ne pas avoir traité les derniers échantillons avant que la session ne se termine, ce qui entraîne des statistiques de résumé manquantes. Correction : toujours appeler stopActivity(_:) en premier, attendre le rappel du delegate pour confirmer .stopped, puis appeler end().

Inférer l’état au lieu d’utiliser le delegate. Les applications qui suivent un état local (isWorkoutActive: Bool) et qui ne câblent jamais le delegate manquent les transitions pilotées par le système (mise en pause automatique, fin automatique au retrait de la montre, états d’erreur). Correction : toujours utiliser le delegate comme source de vérité.

Ce que ce motif signifie pour les applications iOS 26+

Trois points à retenir.

  1. Cartographier explicitement le cycle de vie sur l’état de l’interface. L’interface d’une application d’entraînement comporte des états évidents : non démarré, en préparation, actif, en pause, résumé, terminé. Cartographiez chacun sur un HKWorkoutSessionState. Ne faites pas tourner l’interface sur des booléens improvisés ; liez-la à l’état rapporté par la session via le delegate.

  2. Utiliser prepare() plus une interface de compte à rebours pour toute session qui expose des métriques. Le préchauffage de 3 secondes est la différence entre des données auxquelles l’utilisateur fait confiance et des données qu’il décompte. Le coût est un petit élément d’interface ; le gain est des métriques fiables.

  3. Les sessions d’entraînement iPhone d’iOS 26 nécessitent un code de builder différent. La API de session est partagée ; le côté builder est spécifique à la plateforme. Les applications qui partagent un chemin de code entre iOS et watchOS ont besoin de branches #if os(watchOS) explicites ou d’un wrapper qui abstrait la différence.

L’ensemble du cluster Apple Ecosystem : App Intents typés ; serveurs MCP ; la question du routage ; Foundation Models ; la distinction LLM runtime vs outillage ; trois surfaces ; le motif source unique de vérité ; Deux serveurs MCP ; hooks pour le développement Apple ; Live Activities ; le runtime watchOS ; internes de SwiftUI ; le modèle mental spatial de RealityKit ; discipline de schéma SwiftData ; motifs Liquid Glass ; livraison multiplateforme ; la matrice de plateformes ; Vision framework ; Symbol Effects ; inférence Core ML ; API Writing Tools ; Swift Testing ; Privacy Manifest ; l’accessibilité comme plateforme ; typographie SF Pro ; motifs spatiaux visionOS ; Speech framework ; migrations SwiftData ; moteur de focus tvOS ; internes de @Observable ; protocole Layout SwiftUI ; SF Symbols personnalisés ; AVFoundation HDR ; ce que je refuse d’écrire. Le hub se trouve à la Série Apple Ecosystem. Pour un contexte plus large sur iOS avec des agents IA, consultez le guide iOS Agent Development.

FAQ

L’iPhone utilise-t-il le même HKLiveWorkoutBuilder que l’Apple Watch ?

Oui, depuis iOS 26. La même API HKLiveWorkoutBuilder est livrée sur iOS 26+, iPadOS 26+, Mac Catalyst 26+ et watchOS 5+. Les différences de plateforme se trouvent dans le modèle d’exécution et la disponibilité des capteurs, et non dans la API du builder. Les entraînements iPhone gèrent la confidentialité de l’écran verrouillé et la récupération après crash via le scene delegate (selon la session 322 de la WWDC 2025), ce qui diffère de la garantie d’exécution lors du baissé de poignet de watchOS, mais la API d’accumulation des données est la même.

Quelle est la durée maximale d’un entraînement ?

Il n’y a pas de plafond strict de durée. Les limites pratiques proviennent de la batterie (l’Apple Watch dure environ 6 à 8 heures en entraînement continu) et du stockage (les entraînements avec des données à haute fréquence s’accumulent rapidement). Des applications de course de marathon (entraînements de 12 heures et plus) sont livrées aujourd’hui ; le framework les prend en charge.

Comment gérer la mise en pause automatique ?

Définissez HKWorkoutConfiguration.activityType sur un type qui prend en charge la mise en pause automatique (par exemple, .running). watchOS mettra automatiquement en pause et reprendra en fonction du mouvement de l’utilisateur. Les transitions d’état passent par le delegate ; traitez-les de la même manière que les pauses initiées par l’utilisateur.

Que se passe-t-il si l’utilisateur retire sa montre en milieu d’entraînement ?

La session continue dans son état actuel (typiquement .running). Le système watchOS finira par mettre fin à la session si la montre est retirée du poignet trop longtemps ; le rappel didFailWithError du delegate se déclenche dans ce cas. Les applications avec des sessions inter-appareils (iOS 26+) peuvent passer la main à l’iPhone si l’utilisateur dispose des deux appareils.

Dois-je enregistrer l’entraînement dans HealthKit ?

Presque toujours oui. Appeler builder.finishWorkout(completion:) écrit l’entraînement complet dans HealthKit, ce qui signifie que les données apparaissent dans l’application Activité, dans la liste des entraînements de l’application Santé et dans toutes les autres applications que l’utilisateur a autorisées. Sauter l’enregistrement supprime les données ; le framework ne fournit aucun chemin de récupération.

Comment cela se rapporte-t-il aux autres ajouts récents d’Apple Health ?

iOS 26 / watchOS 26 a élargi la API d’entraînement de deux manières spécifiques : premièrement, en apportant HKWorkoutSession sur iPhone (couvert ci-dessus) ; deuxièmement, en élargissant la liste des types d’activités et la couverture de la détection automatique. L’article watchOS Runtime Contract du cluster couvre le côté exécution ; cet article couvre le côté cycle de vie. Ensemble, ils décrivent la surface complète pour livrer une application d’entraînement.

Références


  1. Documentation Apple Developer : HKWorkoutSession. La classe de session avec les transitions d’états, la configuration et le protocole delegate. 

  2. Documentation Apple Developer : HKWorkoutSessionState. Les cinq cas d’état (.notStarted, .prepared, .running, .paused, .stopped, .ended) et leur sémantique. 

  3. Documentation Apple Developer : HKLiveWorkoutBuilder et HKLiveWorkoutDataSource. La API du builder en direct (watchOS 5+, iOS 26+, iPadOS 26+, Mac Catalyst 26+) et sa source de données. 

  4. Apple Developer : Track workouts with HealthKit on iOS and iPadOS (session 322 de la WWDC 2025). L’extension iOS 26 de HKWorkoutSession à l’iPhone. 

  5. Documentation Apple Developer : HKWorkoutSessionDelegate. Le protocole delegate avec les rappels de transition d’état et d’erreur. 

  6. Documentation Apple Developer : Background Execution on watchOS. Le contrat d’exécution watchOS décrivant quels types de session maintiennent les applications en cours d’exécution lors du baissé de poignet. 

Articles connexes

HealthKit + SwiftUI on iOS 26: Authorization, Sample Types, and Cross-Platform Patterns

Real production patterns from Water (water tracking, HKQuantitySample) and Return (mindful sessions, HKCategorySample). …

17 min de lecture

watchOS Runtime Is a Contract, Not a Background Task

watchOS does not have iOS's background. WKExtendedRuntimeSession is a contract you sign with the system, broken on wrist…

15 min de lecture

The Cleanup Layer Is the Real AI Agent Market

Charlie Labs pivoted from building agents to cleaning up after them. The AI agent market is moving from generation to pr…

15 min de lecture