← Alle Beitrage

watchOS-Laufzeit ist ein Vertrag, kein Hintergrund-Task

Genre: shipped-code. Der Beitrag dokumentiert das watchOS-Laufzeitmuster, das Return produktiv einsetzt. Return ist der SwiftUI-Meditationstimer im App Store; die Watch-App führt einen Mehrzyklus-Timer aus, der weiterzählen muss, wenn der Benutzer das Handgelenk senkt.1 Das Muster, das diese Einschränkung überlebt, ist WKExtendedRuntimeSession plus ein globaler, App-weit gültiger Delegate. Alles andere stirbt in dem Moment, in dem die Watch in den Schlafmodus geht.

watchOS ist nicht iOS mit einem kleineren Bildschirm. Das Laufzeitmodell ist ein anderes. iOS gewährt einer App ein großzügiges Vordergrundbudget und eine schwindende, aber reale Hintergrundlaufzeit über Audio-Sitzungen, Standort-Updates, BGTaskScheduler und eine Handvoll weiterer Möglichkeiten.2 watchOS gewährt der Vordergrund-App ein Budget, das in Sekunden nach dem Senken des Handgelenks gemessen wird, und danach wird die App suspendiert, sofern sie keinen Laufzeitvertrag mit dem System geschlossen hat. Es gibt keine Möglichkeit nach dem Motto „Ich mache da gerade nur etwas im Hintergrund”. Es gibt nur „Ich führe ein Workout, eine Achtsamkeitssitzung, einen intelligenten Wecker, eine Navigationsroute oder eine Gesundheitsüberwachungsaufgabe aus”, und sonst nichts.3

Das Watch-Target von Return ist ein Achtsamkeitstimer. Der Sitzungsvertrag lautet WKBackgroundModes: mindfulness. Die Laufzeit-API ist WKExtendedRuntimeSession. Das Muster, das die Watch-App von kaputt beim Senken des Handgelenks zu übersteht eine 25-minütige Meditation gebracht hat, ist das, was dieser Beitrag beschreibt.

TL;DR

  • watchOS hat keinen Hintergrund im iOS-Stil. Die Vordergrundlaufzeit endet kurz nach dem Senken des Handgelenks, und nur registrierte Sitzungstypen laufen weiter.
  • WKExtendedRuntimeSession ist die API-Oberfläche. Die Sitzung muss einen Sitzungstyp deklarieren; bei einem Meditationstimer ist der Typ implizit über WKBackgroundModes: mindfulness in Info.plist.
  • Der Sitzungsmanager muss auf App-Ebene angesiedelt sein, nicht auf View-Ebene. Der View-Lebenszyklus von SwiftUI dealloziert View-eigene Objekte beim Navigieren; ein deallozierter Sitzungs-Delegate ist eine tote Sitzung, selbst wenn die Sitzung selbst noch läuft.
  • Die Callbacks von WKExtendedRuntimeSessionDelegate sind der Vertrag: didStart, willExpire, didInvalidateWith. Der Expire-Callback wird ausgelöst, bevor das System die Invalidierung erzwingt; Apple beschreibt ihn als das Fenster, in dem die App „abschließen und aufräumen” soll.
  • Ein Senken des Handgelenks ohne aktive Extended Session pausiert den Timer. Ein Senken des Handgelenks mit aktiver Extended Session lässt den Timer weiterlaufen. Die Sitzung ist der Unterschied zwischen „ausgeliefertem Produkt” und „kaputt bei der zweiten Nutzung”.

Das Hintergrundproblem, das watchOS nicht auf iOS-Art löst

iOS-Apps greifen auf mehrere Hintergrund-Möglichkeiten zurück, wenn die App bei ausgeschaltetem Bildschirm weiterlaufen soll:2

  • Eine AVAudioSession mit der Kategorie .playback hält eine Audio-App am Leben, während Musik abgespielt wird.
  • Die Hintergrund-Updates von CLLocationManager halten eine Navigations-App mit einem blauen Balken am Leben.
  • BGTaskScheduler reiht kurze Wartungsarbeiten in eine Warteschlange ein, die das System nach eigenem Zeitplan ausführt.
  • Eine Vordergrund-UI-Erweiterung (Live Activity, CallKit, PushKit) verbindet den App-Prozess mit einer systemgesteuerten Rendering-Oberfläche.

Nichts davon hilft auf watchOS in der Weise, wie Sie es vielleicht annehmen würden. Watch-Apps verfügen nicht über denselben Hintergrund-Task-Scheduler. Sie verfügen nicht über einen im Hintergrund laufenden AVAudioSession.playback-Modus, der den Timer in der Stille weiterzählen lässt. Sie verfügen über genau eine strukturelle Primitive für „Ich möchte weiterlaufen, nachdem der Benutzer das Handgelenk gesenkt hat”, und diese Primitive ist WKExtendedRuntimeSession mit einem deklarierten Sitzungstyp.3

Die Sitzungstypen, die Apple über WKBackgroundModes unterstützt, sind bewusst eng gefasst:4

  • workout-processing (mit HKWorkoutSession für tatsächliche Workouts)
  • mindfulness (für Meditationstimer und Atemübungen)
  • self-care (für geführte Routinen)
  • physical-therapy (für Therapie-Sitzungs-Apps)
  • alarm (für zeitbasierte Weckalarme)
  • underwater-depth (für Tauch- und Tiefenmess-Apps)

Apps, die nicht in eine dieser Kategorien passen, können WKExtendedRuntimeSession nicht verwenden, um nach dem Senken des Handgelenks weiterzulaufen. Audio-Apps greifen auf die mediaPlayback-Audio-Sitzungskategorie und die Now-Playing-Integration über einen anderen Codepfad zurück; Navigations-Apps verwenden Hintergrund-Updates von CLLocationManager. Die Watch ist kein Allzweckcomputer; sie ist ein Gerät mit Akku-Einschränkungen, die das Laufzeitmodell durchsetzt.

Ein Meditationstimer passt zu mindfulness. Der Vertrag: Hintergrundmodus in Info.plist deklarieren, eine WKExtendedRuntimeSession anfordern, die Delegate-Callbacks behandeln, die Sitzung beenden, wenn der Timer endet. Das System gewährt pro Sitzung etwa eine Stunde Laufzeit, wobei das System nach eigenem Ermessen diese Zeit unter thermischer Belastung oder Akku-Druck verkürzen kann.3

Das Muster, das Return einsetzt

Das Muster beginnt mit der Info.plist-Deklaration:4

<key>WKBackgroundModes</key>
<array>
    <string>mindfulness</string>
</array>

Die Modus-Deklaration ist es, was den Sitzungstyp gültig macht. Ohne sie scheitert der Aufruf von WKExtendedRuntimeSession().start() stillschweigend, und die App wird beim Senken des Handgelenks suspendiert, genau wie eine Watch-App ohne jeglichen Hintergrundmodus.

Der Sitzungsmanager selbst muss auf App-Ebene angesiedelt sein. Der View-Lebenszyklus von SwiftUI ist langlebigen, zustandsbehafteten Objekten gegenüber unfreundlich: @StateObject und @State sind auf den View beschränkt, der sie besitzt, und ein Navigations-Push, der den View ersetzt, verwirft den Zustand mit ihm. Eine WKExtendedRuntimeSession, deren Delegate während der Sitzung dealloziert wird, stürzt nicht ab; die Sitzung läuft weiter, aber die Delegate-Callbacks (willExpire, didInvalidateWith) erreichen ein freigegebenes Objekt, was bedeutet, dass das Aufräumen nie geschieht, was wiederum bedeutet, dass der nächste startSession()-Aufruf annimmt, es gäbe keine aktive Sitzung, und eine Duplikat-Sitzung startet.

Das produktiv eingesetzte Muster ist ein Singleton auf App-Ebene. Das folgende Snippet zeigt die strukturelle Form; in Produktion kommt innerhalb jeder Methode noch Logging zur Beobachtbarkeit hinzu:

import SwiftUI
import WatchKit

final class WatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate {
    static let shared = WatchSessionManager()

    private var session: WKExtendedRuntimeSession?

    private override init() {
        super.init()
    }

    var isSessionActive: Bool {
        session != nil
    }

    func startSession() {
        guard session == nil else { return }
        let newSession = WKExtendedRuntimeSession()
        newSession.delegate = self
        newSession.start()
        session = newSession
    }

    func endSession() {
        guard let existing = session else { return }
        existing.invalidate()
        session = nil
    }

    // MARK: - WKExtendedRuntimeSessionDelegate

    func extendedRuntimeSessionDidStart(_ session: WKExtendedRuntimeSession) {}

    func extendedRuntimeSessionWillExpire(_ session: WKExtendedRuntimeSession) {
        // Apple's "about to expire / finish and clean up" hook
    }

    func extendedRuntimeSession(
        _ session: WKExtendedRuntimeSession,
        didInvalidateWith reason: WKExtendedRuntimeSessionInvalidationReason,
        error: Error?
    ) {
        self.session = nil
    }
}

Drei Details zu diesem Singleton, die in der Dokumentation nicht erwähnt werden:

Das static let shared plus @State private var sessionManager = WatchSessionManager.shared auf der @main App-Ebene hält den Manager für die Lebensdauer des Watch-App-Prozesses am Leben. SwiftUI hält Singletons nicht nur deshalb fest, weil ein View sie hält; die obige Bindung ist es, die der Laufzeit mitteilt, die Referenz zu behalten. Ohne die Bindung auf App-Ebene kann ARC den Manager verwerfen, wenn kein View ihn mehr hält.

Die session-Property ist der Schutz gegen Duplikat-Sitzungen. Ein Timer mit einer „Neu starten”-Schaltfläche kann startSession() aus mehreren Pfaden aufrufen; die Prüfung guard session == nil ist die Sperre. Zwei gleichzeitige Extended Sessions führen zu unvorhersehbarem Verhalten: Manchmal gelingt die zweite und die erste wird verwaist, manchmal scheitert der Start-Aufruf stillschweigend. Die Single-Session-Invariante verhindert die gesamte Klasse dieser Probleme.

Die Delegate-Callbacks loggen, agieren aber selten. Der didStart-Callback wird einmal pro Sitzung ausgelöst und ist ein nützlicher Hook für die Beobachtbarkeit; der willExpire-Callback wird ausgelöst, bevor das System die Invalidierung erzwingt, und ist die Stelle, an der Apple erwartet, dass die App „abschließt und aufräumt”; der didInvalidateWith-Callback ist die Stelle, an der die Sitzungsreferenz gelöscht wird, damit der nächste startSession()-Aufruf funktioniert. Das Muster in der Produktion lautet Callbacks aktualisieren den Zustand, die Zustandsmaschine erledigt die Arbeit, nicht Callbacks erledigen die Arbeit direkt.

Der Timer-Manager ruft den Sitzungsmanager bei jedem Übergang auf, der ändert, ob der Timer aktiv zählt:

final class WatchTimerManager: ObservableObject {
    func start() {
        startExtendedSession()        // -> WatchSessionManager.shared.startSession()
        // ... start the timer state machine ...
    }

    func pause() {
        timer?.invalidate()
        isRunning = false
        endExtendedSession()          // -> WatchSessionManager.shared.endSession()
    }

    func reset() {
        // ... clear timer state ...
        endExtendedSession()
    }

    private func completeCycle() {
        // ... last cycle handling ...
        endExtendedSession()          // ends on final completion
    }
}

Die Sitzung endet beim Pausieren, beim Zurücksetzen und beim Abschluss des letzten Zyklus. Die Produkt-Begründung: Eine pausierte Meditation muss kein Laufzeitbudget weiter belegen, das das System unter mindfulness gewährt; ein Resume aus der Pause heraus erwirbt eine frische Sitzung. Der Produkt-Preis ist, dass eine bei gesenktem Handgelenk pausierte Sitzung nicht allein durch das Heben des Handgelenks fortgesetzt werden kann; der Benutzer muss die App in den Vordergrund zurückbringen, um fortzusetzen. Der Produkt-Gewinn ist, dass die Akkukosten eines pausierten Timers auf null gehen und das System keine veraltete Sitzung sieht.

Das Senken des Handgelenks ist der Test

Das Testen von watchOS im Simulator ist eine höfliche Fiktion. Der Simulator setzt das Laufzeitmodell beim Senken des Handgelenks nicht so durch, wie es eine echte Apple Watch tut. Der Simulator hält die App im Vordergrund, solange das Simulator-Fenster den Fokus hat; eine Extended Runtime Session im Simulator sieht identisch aus wie gar keine Sitzung, weil die Vordergrund-App so oder so weiterläuft.

Der eigentliche Test findet auf einer echten Apple Watch statt:5

  1. Den Timer starten.
  2. Das Handgelenk senken (oder die Seitentaste drücken, um den Bildschirm zu sperren).
  3. 30 Sekunden warten.
  4. Das Handgelenk wieder heben.

Ohne aktive Extended Runtime Session wird die Watch-App suspendiert; der Timer-Zustand ist im Moment des Senkens des Handgelenks eingefroren und setzt von diesem eingefrorenen Zustand aus fort. Bei einer 5-minütigen Meditation, bei der der Benutzer die Augen schließt, ist der Bug unsichtbar, bis der Timer um genau die Zeit falsch ist, in der die Augen geschlossen waren.

Mit aktiver Extended Runtime Session zählt der Timer weiter. Das Heben des Handgelenks zeigt den Timer an der korrekten verstrichenen Position. Das Audiosignal (falls der Timer am Ende eines abspielt) wird zur korrekten Wanduhrzeit ausgelöst, nicht zur Zeit des Hebens des Handgelenks.

Das Szenario des Senkens des Handgelenks war der Bug, den Return in v1 ausgeliefert und in v2 gepatcht hat. Der Fix ist das oben beschriebene Singleton-Muster; der Bug war eine WatchSessionManager-Instanz, die von einem SwiftUI-View gehalten wurde, der beim Navigations-Push dealloziert wurde. Die Sitzung lief technisch gesehen auf der Systemseite, aber der Delegate war freigegeben; der nächste Sitzungs-Start-Aufruf war stillschweigend ein No-Op, weil die session-Property des Managers auf einem nun toten Objekt gesetzt worden war. Tests auf echter Hardware bringen den Fehler in Sekunden zum Vorschein. Tests im Simulator bringen ihn nie zum Vorschein.

Was die Delegate-Callbacks Ihnen tatsächlich mitteilen

WKExtendedRuntimeSessionInvalidationReason zählt die Möglichkeiten auf, wie eine Sitzung enden kann:6

Grund Wann er auftritt
none Die Sitzung wurde explizit invalidiert, indem die App invalidate() aufgerufen hat
sessionInProgress Eine Sitzung desselben Typs läuft bereits
expired Das vom System auferlegte Zeitlimit wurde erreicht
resignedFrontmost Eine andere App wurde in den Vordergrund gebracht, während die Sitzung lief
suppressedBySystem Das System hat die Sitzung unterdrückt (Energiesparmodus, thermischer Druck)
error Ein nicht behebbarer Fehler ist aufgetreten; den Parameter error prüfen

Die Gründe, die für das Produktdesign relevant sind:

expired bedeutet, dass der Benutzer die volle Sitzung erhalten hat, die Sie angefordert haben. Die Sitzung ist auf natürliche Weise zu Ende gegangen. Die längste Meditationsdauer von Return beträgt 60 Minuten, was genau am Rand dessen liegt, was mindfulness-Sitzungen typischerweise gewährt wird. Eine 90-minütige Meditation würde routinemäßig auf expired stoßen, und der Timer würde mitten in der Sitzung sterben. Die Produktentscheidung besteht darin, die verfügbaren Dauern auf das zu begrenzen, was das Laufzeitmodell tatsächlich liefern kann.

resignedFrontmost bedeutet, dass der Benutzer eine andere Watch-App geöffnet hat und Ihre Sitzung verloren hat. Watch-Benutzer sind gut darin, zu einer anderen App zu wischen und es dann zu vergessen. Die Produktentscheidung besteht entweder darin, beim Verlassen zu pausieren (Zustand bleibt erhalten, der Benutzer kann zurückkommen) oder beim Verlassen zu beenden (Sitzung beendet, der Benutzer erhält ein Signal „Sie haben früh aufgehört”). Return wählt das Pausieren beim Verlassen, sodass der Benutzer mitten in der Meditation einen Anruf annehmen und dann zurückkehren kann.

suppressedBySystem ist die höfliche Version von „die Watch ist heiß”. Ein watchOS-Gerät unter thermischem Druck oder mit niedrigem Akku kann eine Extended Runtime Session widerrufen, sogar ohne Fehlverhalten der App. Der Sitzungsmanager muss den Fall elegant behandeln: die Referenz löschen, eine nicht blockierende Warnung anzeigen und nicht in einen Zustand geraten, in dem er versucht, eine Sitzung neu zu starten, die das System gerade abgelehnt hat.

Der willExpire-Callback wird ausgelöst, wenn die Sitzung kurz vor dem Ablauf steht, und ist als der Moment dokumentiert, in dem die App „abschließen und aufräumen” soll.3 Der Callback ist die Stelle, an der eine App einen finalen Zustands-Snapshot schreiben, einen abschließenden Audio-Cue abspielen oder eine UI „Sitzung endet bald” präsentieren kann. Return loggt heute lediglich den Callback; reichhaltigeres Aufräumen (HealthKit-Logbucheintrag, Audio-Fade-Out) geschieht auf den Reset- und Abschluss-Pfaden des Timers und steht auf der Liste was ich anders bauen würde für das willExpire-Fenster.

Was ich anders bauen würde

Zwei Dinge, wenn Return von Grund auf neu starten würde.

HKWorkoutSession für jede Sitzung verwenden, deren Wert mit der HealthKit-Integration steigt. Ein Meditationstimer steht an der Grenze zwischen mindfulness und workout-processing. Mindfulness war für v1 die richtige Wahl, weil das Datenmodell einfacher ist und die Benutzererwartung lautet: „Das ist Meditation, kein Workout.” HKWorkoutSession bietet eine granularere HealthKit-Integration (Sitzungsstart, Sitzungsende, Segmente, Ereignisse) und stellt eine reichhaltigere LiveWorkoutBuilder-Schnittstelle zur Datensammlung bereit. Die architektonische Einschätzung, keine dokumentierte Apple-Garantie: Für eine App, deren Wert von detaillierter Sitzungstelemetrie abhängt, behandelt der Workout-Sitzungs-Pfad Strukturen, die WKExtendedRuntimeSession nicht bietet.

Vom ersten Tag an eine Beobachtbarkeitsoberfläche für den Sitzungszustand hinzufügen. Die erste Version von Return hat Sitzungsereignisse in die Konsole geloggt. Die zweite Version fügte eine Sichtbarkeit des Sitzungszustands auf dem Gerät zum Debuggen hinzu. Die dritte würde einen Entwicklermodus-Schalter offenlegen, der dem Benutzer den Verlauf der Sitzungsgründe anzeigt, wenn etwas schiefgeht, anstatt die Sitzungsinvalidierung als Black Box zu behandeln. Die watchOS-Laufzeit ist undurchsichtig; die Debug-Oberfläche muss das kompensieren.

Wann WKExtendedRuntimeSession die falsche Antwort ist

Drei Fälle, in denen der Sitzungstyp nicht passt:

Workouts, die Segmentmarker, Herzfrequenzströme oder aktive Kalorienverfolgung benötigen. Verwenden Sie HKWorkoutSession direkt mit einem HKLiveWorkoutBuilder. Die Workout-API ist Apples dokumentierter Pfad für tatsächliche Workouts (und Gehmeditationen oder anstrengende Aktivitäten); WKExtendedRuntimeSession ist der dokumentierte Pfad für nicht-Workout-Sitzungen wie Achtsamkeit oder Wecker. Eine Meditations-App benötigt kein Workout; eine Couch-to-5K-App schon.

Audio-Wiedergabe, die eine Now-Playing-Oberfläche benötigt. Verwenden Sie eine AVAudioSession, die für Wiedergabe konfiguriert ist, zusammen mit watchOS-Audio-Sitzungs-Berechtigungen; die Now-Playing-Integration plus die Systemwiedergabe-Oberfläche sind das, was Audio-Apps wollen, und der Audio-Pfad ist vollständig getrennt von WKExtendedRuntimeSession. WKExtendedRuntimeSession bietet Ihnen weder Now Playing noch das System-Audio-Routing.

Langlaufende Datensynchronisation ohne Benutzerwahrnehmung. Verwenden Sie WKApplicationRefreshBackgroundTask für periodische Aktualisierungsfenster, die das System plant. Der Benutzer ist nicht in der App; die App muss nicht weiterlaufen; sie muss kurz aufwachen und aktualisieren. Die beiden Modelle Hintergrund-Task und Extended Runtime Session bedienen sehr unterschiedliche Bedürfnisse.

Was das Muster für Apps bedeutet, die auf watchOS 11+ ausgeliefert werden

Drei Erkenntnisse.

  1. Das Watch-Laufzeitmodell ist Opt-in. Wählen Sie einen Sitzungstyp und leben Sie innerhalb seiner Regeln. Apps, die versuchen, „allgemeine Hintergrundarbeit” auf watchOS zu erledigen, werden verlieren. Wählen Sie mindfulness, workout-processing, self-care, physical-therapy, alarm oder underwater-depth und entwerfen Sie die Benutzererfahrung rund um das Laufzeitbudget, das mit dem von Ihnen gewählten Sitzungstyp einhergeht.

  2. Der Sitzungs-Delegate muss auf App-Ebene angesiedelt sein. Der View-Lebenszyklus von SwiftUI schützt langlebige zustandsbehaftete Objekte nicht. Ein static let shared-Singleton, das auf der @main App-Ebene gebunden ist, ist das kleinste Muster, das Navigations-Pushes, View-Ersetzungen und das normale Deallokationsverhalten von SwiftUI übersteht.

  3. Auf echter Hardware testen. Der Simulator setzt das Laufzeitmodell beim Senken des Handgelenks nicht durch. Der Bug, den eine Watch-App im Simulator nicht testen kann, ist der Bug, den sie an die Benutzer ausliefert.

Lesen Sie diesen Beitrag zusammen mit meinen früheren Artikeln zur selben App-Familie: plattformübergreifendes SwiftUI-Shipping (Return wird auf iPhone, iPad, Watch, Mac und Apple TV ausgeliefert); die Live-Activities-Zustandsmaschine (die iOS-seitige Oberfläche für denselben Timer); HealthKit-Muster (wo die Achtsamkeitssitzungen der Watch in den Health-Daten des Benutzers landen). Die vollständige Sammlung finden Sie auf dem Hub der Apple Ecosystem Series. Für einen breiteren Kontext zu iOS mit AI-Agenten siehe den iOS Agent Development Guide.

FAQ

Was ist eine watchOS Extended Runtime Session?

Eine watchOS Extended Runtime Session (WKExtendedRuntimeSession) ist die API, die eine Watch-App verwendet, um weiterzulaufen, nachdem der Benutzer das Handgelenk gesenkt hat. Die Sitzung muss einen Typ deklarieren (mindfulness, workout-processing, alarm usw.) über WKBackgroundModes in Info.plist. Ohne aktive Extended Session suspendiert watchOS die App kurz nach dem Senken des Handgelenks.

Warum stoppt mein watchOS-Timer das Zählen, wenn der Benutzer das Handgelenk senkt?

Die Watch-App wird kurz nach dem Senken des Handgelenks suspendiert, sofern keine aktive WKExtendedRuntimeSession eines unterstützten Typs läuft. Ein Timer-Manager, der keine solche Sitzung startet, sieht seine Hintergrundlaufzeit abgeschnitten, und der Timer-Zustand friert im Moment des Senkens des Handgelenks ein, bis der Benutzer das Handgelenk wieder hebt.

Was ist der Unterschied zwischen WKExtendedRuntimeSession und HKWorkoutSession?

WKExtendedRuntimeSession ist die universell einsetzbare Extended-Runtime-API für nicht-Workout-Sitzungen wie Achtsamkeit, Wecker oder Selbstpflege. HKWorkoutSession ist die API für tatsächliche Workouts; sie integriert sich mit HealthKit, unterstützt Segmentmarker und ist der dokumentierte Pfad für Gehmeditationen oder anstrengende Aktivitäten. Achtsamkeits-Apps ohne Workout-taugliche Telemetrie verwenden die erste; Workout-Apps verwenden die zweite.

Kann das System meine Extended Runtime Session widerrufen?

Ja. WKExtendedRuntimeSessionInvalidationReason umfasst expired (System-Zeitlimit erreicht), resignedFrontmost (eine andere Watch-App wurde in den Vordergrund gebracht) und suppressedBySystem (Energiesparmodus oder thermischer Druck). Der Sitzungsmanager muss jeden Fall sauber behandeln: Die Referenz wird gelöscht, der Timer-Zustand reagiert angemessen, und der nächste Sitzungs-Start-Aufruf funktioniert korrekt.

Wo sollte der Sitzungsmanager in einer SwiftUI-watchOS-App leben?

Auf App-Ebene, als ein vom @main App-Struct gebundenes Singleton. Der View-bezogene Zustand von SwiftUI (@State, @StateObject) wird bei Navigations-Pushes, View-Ersetzungen oder dem Übergang der App in den Hintergrund dealloziert. Ein View-eigener Sitzungs-Delegate, der mitten in der Sitzung freigegeben wird, führt dazu, dass die Sitzungsreferenz leckt und verhindert, dass nachfolgende Sitzungen sauber starten.

Referenzen


  1. Return des Autors, ein SwiftUI-Meditationstimer, der am 21. April 2026 im App Store veröffentlicht wurde, verfügbar für iPhone, iPad, Mac, Apple Watch und Apple TV. Die Watch-App verwendet WKExtendedRuntimeSession mit dem Hintergrundmodus mindfulness für die Laufzeit des Zyklus-Timers. 

  2. Apple Developer, „About the background execution sequence”. iOS-seitige Hintergrundlaufzeit-Möglichkeiten (Audio-Sitzungen, Standort, BGTaskScheduler) und wie sie sich von watchOS unterscheiden. 

  3. Apple Developer, „WKExtendedRuntimeSession”. Sitzungstypen, Lebenszyklus, Delegate-Callbacks, Laufzeitlimits und der WKBackgroundModes-Schlüssel in Info.plist. 

  4. Apple Developer, „Information Property List: WKBackgroundModes”. Unterstützte Sitzungstyp-Strings: workout-processing, mindfulness, self-care, physical-therapy, alarm, underwater-depth

  5. Apple Developer, „Building a watchOS app” und die WatchKit-Test-Hinweise. Das Laufzeitverhalten auf echten Geräten ist im watchOS-Simulator nicht reproduzierbar; der Simulator setzt die Suspendierung beim Senken des Handgelenks nicht durch. 

  6. Apple Developer, „WKExtendedRuntimeSessionInvalidationReason”. Aufzählungsfälle: none, sessionInProgress, expired, resignedFrontmost, suppressedBySystem, error

Verwandte Beiträge

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. Lesezeit

What SwiftUI Is Made Of

SwiftUI is a result-builder DSL on top of a value-typed View tree. Once the substrate is visible, AnyView, Group, and Vi…

17 Min. Lesezeit

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. Lesezeit