← Wszystkie wpisy

Cykl życia treningu w HealthKit: stany HKWorkoutSession i międzyplatformowa powierzchnia w iOS 26

HKWorkoutSession to maszyna stanów treningu w HealthKit. Sesja przechodzi przez sześć stanów (.notStarted, .prepared, .running, .paused, .stopped, .ended), udostępnia zdarzenia cyklu życia poprzez HKWorkoutSessionDelegate i (od iOS 26) działa na iPhone’ie oprócz Apple Watch1. HKLiveWorkoutBuilder sparowany z sesją zbiera próbki i zdarzenia przyrostowo; iOS 26 przyniósł ten sam builder API na iPhone’a. Wpis Kontrakt środowiska uruchomieniowego watchOS z tego klastra argumentował, że aplikacje watchOS potrzebują rozpoznawanego typu sesji, aby działać w tle; HKWorkoutSession jest jednym z takich typów sesji, a stany cyklu życia mapują się bezpośrednio na model środowiska uruchomieniowego.

Wpis omawia cykl życia treningu w oparciu o dokumentację Apple. Ramą jest „co pozwala każdy stan i co wyzwala każde przejście”, ponieważ aplikacje treningowe, które źle zarządzają cyklem życia, albo tracą dane (zbyt wczesne wyjście ze stanu running), albo wyczerpują baterię (brak wyjścia ze stanu running w ogóle).

TL;DR

  • Cykl życia HKWorkoutSession: notStartedpreparedrunning → (opcjonalnie pausedrunning) → stoppedended. Przejścia są raportowane przez HKWorkoutSessionDelegate.workoutSession(_:didChangeTo:from:date:)2.
  • HKLiveWorkoutBuilder to akumulator danych na żywo sparowany z sesją treningową. Powstał na watchOS w 2018 roku i został udostępniony na iOS 26+, iPadOS 26+ oraz Mac Catalyst 26+. Treningi na iPhone używają tego samego HKLiveWorkoutBuilder API co Apple Watch, z różnicami specyficznymi dla platformy w modelu środowiska uruchomieniowego i dostępności czujników3.
  • Metoda prepare() na sesji rozgrzewa czujniki przed uruchomieniem właściwego treningu przez startActivity(_:). Apple zaleca trzysekundowe odliczanie w UI między prepare() a startActivity(_:), aby dać czujnikom tętna i zewnętrznym urządzeniom Bluetooth czas na połączenie.
  • Stan stopped jest przejściowy: aplikacje mogą sfinalizować w nim metryki, ale nie mogą wznowić sesji. Wywołanie end() przechodzi do ended, który jest stanem końcowym.
  • Wpis Kontrakt środowiska uruchomieniowego watchOS z tego klastra omawia, jak sesja treningowa utrzymuje działanie aplikacji watchOS przy opuszczeniu nadgarstka. Maszyna stanów cyklu życia i kontrakt utrzymania działania środowiska uruchomieniowego to dwie połowy tej samej powierzchni.

Sześć stanów

HKWorkoutSessionState wylicza cykl życia2:

.notStarted. Sesja została utworzona, ale nie przygotowana. Czujniki nie są rozgrzane; aplikacja nie jest jeszcze uznawana za aktywnego hosta treningu. Przejście do .prepared następuje, gdy aplikacja wywoła prepare().

.prepared. Sesja wywołała prepare(); czujniki się rozgrzewają, ale trening jeszcze się nie rozpoczął. Monitory tętna nawiązują połączenie, czujniki ruchu się inicjalizują, GPS uzyskuje pozycję. Wzorzec widoczny dla użytkownika to trzysekundowe odliczanie („Przygotuj się… 3, 2, 1, START!”); w tym oknie system ma czas na uzyskanie czystego sygnału, dzięki czemu pierwsze metryki w stanie running są dokładne.

.running. Stan aktywnego treningu. Aplikacja zbiera metryki, wyświetla dane na żywo i (na watchOS) utrzymuje włączony ekran dzięki kontraktowi środowiska uruchomieniowego workout-active. Przejście do .running następuje przez startActivity(_:).

.paused. Stan wstrzymania przez użytkownika. Aplikacja nie zbiera już aktywnych metryk (np. dystansu), ale sesja jest zachowana; wywołanie resume() powraca do .running. Cykl pauzy/wznowienia może wystąpić dowolną liczbę razy w obrębie jednej sesji.

.stopped. Przejściowy stan po treningu. Sesja zakończyła swoją aktywną fazę, ale nie została jeszcze sfinalizowana; live builder może wciąż finalizować metryki. Ze stanu .stopped wywołanie end() przechodzi do .ended. Aplikacja nie może wznowić ze stanu .stopped.

.ended. Stan końcowy. Sesja jest zakończona; live builder otrzymał polecenie finalizacji; trening jest zapisany w HealthKit (jeśli aplikacja wywołała finishWorkout(completion:) na builderze). Po wejściu w .ended sesja nie jest już manipulowalna.

Diagram stanów ma jeden konkretny haczyk: nie ma ścieżki ze .stopped z powrotem do .running. Trening, który użytkownik chciałby „cofnąć”, wymaga uruchomienia nowej sesji, a nie wznowienia starej.

Metody sterujące przejściami

HKWorkoutSession udostępnia następujące metody do przejść stanów1:

  • prepare(). Przechodzi z .notStarted do .prepared. Rozgrzewa czujniki.
  • startActivity(with: Date). Przechodzi z .prepared do .running. Parametr Date pozwala aplikacji ustawić oficjalny czas rozpoczęcia (zwykle .now).
  • pause(). Przechodzi z .running do .paused.
  • resume(). Przechodzi z .paused z powrotem do .running.
  • stopActivity(with: Date). Przechodzi z .running (lub .paused) do .stopped. Date to oficjalny czas zakończenia.
  • end(). Przechodzi ze .stopped do .ended.

Wzorzec od prepare() do startActivity(_:) to okno rozgrzewki. Wzorzec od stopActivity(_:) do end() to okno czyszczenia: live builder dostaje szansę dodania ostatecznych próbek, zanim sesja zostanie zakończona.

HKLiveWorkoutBuilder na watchOS i iPhone

HKLiveWorkoutBuilder to akumulator danych na żywo sparowany z sesją3. Builder pojawił się na watchOS w watchOS 5 i został rozszerzony na iOS 26+, iPadOS 26+ oraz Mac Catalyst 26+. Cykl życia buildera łączy się z cyklem życia sesji:

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

Trzy elementy spajają trening:

  • HKWorkoutConfiguration określa typ aktywności i lokalizację. Typ aktywności steruje wyborem metryk (trening biegowy zbiera tempo, trening na rowerku stacjonarnym nie).
  • HKLiveWorkoutDataSource to most między konfiguracją czujników sesji a akumulacją danych w builderze. Źródło danych publikuje próbki; builder je odbiera i zapisuje.
  • HKLiveWorkoutBuilder przechowuje stan trwającego treningu i finalizuje zapisany obiekt HKWorkout.

Wzorzec jest przyrostowy: próbki płyną nieprzerwanie podczas stanu .running; builder integruje je w bieżące statystyki; ostateczne finishWorkout() zapisuje kompletny trening do HealthKit.

iOS 26: treningi na iPhone

iOS 26 przyniósł HKWorkoutSession na iPhone’a z tym samym HKLiveWorkoutBuilder i źródłem danych API, których używa watchOS4. Konstrukcja jest taka sama; różnice specyficzne dla platformy dotyczą modelu środowiska uruchomieniowego, dostępności czujników i obsługi prywatności, a nie powierzchni API.

Przypadki użycia, które umożliwia API treningu w iOS 26: - Aplikacje treningowe traktujące telefon jako towarzysza (iPhone przechowuje dane z monitora tętna obok sesji Watch). - Aplikacje fitness wyłącznie na iPhone’a dla użytkowników bez Apple Watch, w których iPhone śledzi sesję poprzez wbudowane czujniki i podłączone akcesoria. - Ciągłość sesji między urządzeniami: sesja Apple Watch przekazywana do iPhone’a (użytkownik zdejmuje Watcha, ale chce, aby iPhone kontynuował śledzenie) lub odwrotnie.

Różnice platformowe warte wymienienia: - Dostępność czujników. iPhone ma akcelerometr i GPS, ale nie ma wbudowanego czujnika tętna. Aplikacje, które potrzebują tętna w treningach na iOS, parują się z paskiem Bluetooth do pomiaru tętna lub odczytują dane z podłączonego Apple Watch przez HealthKit. - Model środowiska uruchomieniowego. Środowisko workout-active na Apple Watch gwarantuje ciągły dostęp do czujników podczas opuszczenia nadgarstka. Środowisko uruchomieniowe iPhone’a opiera się na zwykłym cyklu życia foreground/background systemu plus odzyskiwaniu po awarii przez scene delegate (omówione na sesji 322 WWDC 2025), co stanowi inny kształt gwarancji. - Prywatność i zachowanie ekranu blokady. Treningi na iPhone’ie działające przy zablokowanym urządzeniu wymagają jawnej konfiguracji, aby nadal zbierać próbki, ponieważ ekran blokady stanowi mocniejszą granicę prywatności niż opuszczenie nadgarstka.

Protokół delegata

HKWorkoutSessionDelegate raportuje przejścia stanów i błędy5:

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

Delegat jest jedynym źródłem prawdy o przejściach stanów. Aplikacje, które wnioskują stan z wywołań metod (wywołałem startActivity(), więc teraz jesteśmy w running), pomijają zmiany stanu wprowadzane przez system (auto-pauza, gdy użytkownik jest nieruchomy, auto-zakończenie, gdy zegarek zostanie zdjęty). Wzorzec sterowany delegatem jest poprawny.

Kontrakt środowiska uruchomieniowego

HKWorkoutSession jest jednym z typów sesji, które watchOS rozpoznaje, aby utrzymać aplikację działającą w tle, obok sesji uważności, alarmu i nagrywania dźwięku6. Kontrakt: gdy sesja jest w stanie .prepared, .running, .paused lub .stopped, aplikacja kontynuuje działanie; ekran budzi się, gdy użytkownik podniesie nadgarstek; czujniki strumieniują dane do aplikacji nieprzerwanie.

Wpis Kontrakt środowiska uruchomieniowego watchOS z tego klastra omawia to szczegółowo. Istotny punkt dla aplikacji treningowych: maszyna stanów cyklu życia jest tym, co mówi watchOS „utrzymuj tę aplikację działającą”; przejście do .ended zwalnia kontrakt i pozwala systemowi zawiesić aplikację.

Praktyczna konsekwencja: nie należy kończyć sesji treningowej przedwcześnie. Jeśli użytkownik odejdzie od treningu, by odebrać telefon, i wróci, sesja powinna pozostać w .running (lub być wstrzymana przez pause()), a nie zakończona. Zakończenie i ponowne uruchomienie traci dane oraz ciągłość środowiska uruchomieniowego.

Częste błędy

Trzy wzorce z logów awarii aplikacji treningowych:

Pomijanie prepare(). Aplikacje, które wywołują startActivity(_:) bez wcześniejszego wywołania prepare(), produkują treningi, w których pierwsze 5–10 sekund danych tętna jest niewiarygodne (czujnik nie był rozgrzany) lub brakujące (pasek Bluetooth do pomiaru tętna nie zdążył się połączyć). Rozwiązanie: zawsze wywoływać prepare(), pokazać krótkie odliczanie w UI, a następnie startActivity(_:).

Wywoływanie end() bezpośrednio z .running. Pominięcie .stopped pomija okno finalizacji metryk. Live builder może nie zdążyć przetworzyć ostatecznych próbek przed zakończeniem sesji, co prowadzi do brakujących statystyk podsumowujących. Rozwiązanie: zawsze najpierw wywołać stopActivity(_:), poczekać na callback delegata potwierdzający .stopped, a potem wywołać end().

Wnioskowanie stanu zamiast używania delegata. Aplikacje, które śledzą lokalny stan (isWorkoutActive: Bool) i nigdy nie podpinają delegata, pomijają przejścia sterowane przez system (auto-pauza, auto-zakończenie po zdjęciu zegarka, stany błędu). Rozwiązanie: zawsze używać delegata jako źródła prawdy.

Co ten wzorzec oznacza dla aplikacji w iOS 26+

Trzy wnioski.

  1. Należy mapować cykl życia na stan UI w sposób jawny. UI aplikacji treningowej ma oczywiste stany: nierozpoczęty, przygotowanie, aktywny, wstrzymany, podsumowanie, zakończony. Każdy z nich należy zmapować na HKWorkoutSessionState. Nie warto sterować UI z doraźnych booleanów; trzeba je wiązać ze stanem raportowanym przez sesję poprzez delegata.

  2. Warto używać prepare() plus odliczania w UI dla każdej sesji, która eksponuje metryki. Trzysekundowa rozgrzewka to różnica między danymi, którym użytkownik ufa, a danymi, które dyskontuje. Kosztem jest mały element UI; zyskiem są wiarygodne metryki.

  3. Sesje treningowe iPhone’a w iOS 26 wymagają innego kodu buildera. API sesji jest współdzielone; strona buildera zależy od platformy. Aplikacje współdzielące ścieżkę kodu między iOS i watchOS potrzebują jawnych gałęzi #if os(watchOS) lub wrappera, który abstrahuje różnicę.

Pełny klaster Apple Ecosystem: typowane App Intents; serwery MCP; pytanie o routing; Foundation Models; rozróżnienie środowisko uruchomieniowe vs LLM narzędziowe; trzy powierzchnie; wzorzec single source of truth; Dwa serwery MCP; hooks dla Apple development; Live Activities; środowisko uruchomieniowe watchOS; wewnętrzności SwiftUI; model przestrzennego myślenia RealityKit; dyscyplina schematu SwiftData; wzorce Liquid Glass; wieloplatformowe wydawanie; macierz platform; Vision framework; Symbol Effects; inferencja Core ML; Writing Tools API; Swift Testing; Privacy Manifest; Dostępność jako platforma; typografia SF Pro; wzorce przestrzenne visionOS; Speech framework; migracje SwiftData; silnik fokusa tvOS; wewnętrzności @Observable; protokół Layout w SwiftUI; niestandardowe SF Symbols; HDR w AVFoundation; o czym odmawiam pisać. Centrum znajduje się w Apple Ecosystem Series. Szerszy kontekst iOS-z-AI-agentami opisuje przewodnik iOS Agent Development.

FAQ

Czy iPhone używa tego samego HKLiveWorkoutBuilder co Apple Watch?

Tak, od iOS 26. To samo HKLiveWorkoutBuilder API jest dostępne na iOS 26+, iPadOS 26+, Mac Catalyst 26+ oraz watchOS 5+. Różnice platformowe dotyczą modelu środowiska uruchomieniowego i dostępności czujników, a nie API buildera. Treningi na iPhone’ie obsługują prywatność na ekranie blokady i odzyskiwanie po awarii przez scene delegate (zgodnie z sesją 322 WWDC 2025), co różni się od gwarancji środowiska uruchomieniowego watchOS przy opuszczeniu nadgarstka, ale API akumulacji danych jest takie samo.

Jaki jest maksymalny czas trwania treningu?

Nie ma sztywnego limitu czasu trwania. Praktyczne ograniczenia wynikają z baterii (Apple Watch wytrzymuje ok. 6–8 godzin ciągłego treningu) oraz pamięci (treningi z danymi o wysokiej częstotliwości szybko się akumulują). Aplikacje do biegania maratonów (treningi 12+ godzin) są dziś dostępne; framework je wspiera.

Jak obsłużyć auto-pauzę?

Należy ustawić HKWorkoutConfiguration.activityType na taki, który wspiera auto-pauzę (np. .running). watchOS automatycznie wstrzyma i wznowi sesję na podstawie ruchu użytkownika. Przejścia stanów płyną przez delegata; należy traktować je tak samo jak pauzy inicjowane przez użytkownika.

Co się stanie, jeśli użytkownik zdejmie zegarek w trakcie treningu?

Sesja kontynuuje pracę w swoim bieżącym stanie (zwykle .running). System watchOS ostatecznie zakończy sesję, jeśli zegarek będzie zdjęty z nadgarstka zbyt długo; w tym momencie wystrzeli callback didFailWithError delegata. Aplikacje z sesjami międzyurządzeniowymi (iOS 26+) mogą przekazać sesję do iPhone’a, jeśli użytkownik posiada oba urządzenia.

Czy powinienem zapisać trening do HealthKit?

Prawie zawsze tak. Wywołanie builder.finishWorkout(completion:) zapisuje kompletny trening do HealthKit, co oznacza, że dane pojawiają się w aplikacji Aktywność, na liście treningów aplikacji Zdrowie i w innych aplikacjach, które użytkownik autoryzował. Pominięcie zapisu odrzuca dane; framework nie zapewnia żadnej ścieżki odzyskiwania.

Jak to się ma do innych niedawnych dodatków Apple Health?

iOS 26 / watchOS 26 rozszerzyły API treningu na dwa konkretne sposoby: po pierwsze, udostępniając HKWorkoutSession na iPhone’a (omówione powyżej); po drugie, poszerzając listę typów aktywności i pokrycie auto-detekcji. Wpis Kontrakt środowiska uruchomieniowego watchOS z tego klastra omawia stronę środowiska uruchomieniowego; ten wpis omawia stronę cyklu życia. Razem opisują pełną powierzchnię potrzebną do wydania aplikacji treningowej.

Bibliografia


  1. Dokumentacja Apple Developer: HKWorkoutSession. Klasa sesji z przejściami stanów, konfiguracją i protokołem delegata. 

  2. Dokumentacja Apple Developer: HKWorkoutSessionState. Pięć przypadków stanów (.notStarted, .prepared, .running, .paused, .stopped, .ended) i ich semantyka. 

  3. Dokumentacja Apple Developer: HKLiveWorkoutBuilder i HKLiveWorkoutDataSource. API live buildera (watchOS 5+, iOS 26+, iPadOS 26+, Mac Catalyst 26+) i jego źródło danych. 

  4. Apple Developer: Track workouts with HealthKit on iOS and iPadOS (sesja 322 WWDC 2025). Rozszerzenie HKWorkoutSession na iPhone’a w iOS 26. 

  5. Dokumentacja Apple Developer: HKWorkoutSessionDelegate. Protokół delegata z callbackami przejść stanów i błędów. 

  6. Dokumentacja Apple Developer: Background Execution on watchOS. Kontrakt środowiska uruchomieniowego watchOS opisujący, które typy sesji utrzymują działanie aplikacji przy opuszczeniu nadgarstka. 

Powiązane artykuły

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 czytania

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 czytania

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 czytania