Der Lauf, den ich an ein Beta-OS verlor
Ein 6-km-Lauf in der Juli-Hitze, bei dem meine eigene App weder aufzeichnete noch stoppte, die 1,5 km, die es mich kostete, und der watchOS-27-Beta-Bug, den ich in einem gegen das alte SDK gebauten Build reparieren musste.

Letzten Dienstag bin ich 6 km laufen gegangen und konnte meine eigene App nicht benutzen. Es war heiß, so ein Juli-Nachmittag, an dem sich Lieboch in eine Bratpfanne verwandelt, und ich startete wie immer mit der Watch am Handgelenk und Runara auf dem Bildschirm. Ich drückte Start. Der Countdown ploppte, GO blitzte, und dann: nichts. Keine Herzfrequenz. Kein Tempo, das irgendetwas bedeutete. Und als ich aufgab und stoppen wollte, tat auch Stopp nichts; das Training saß einfach da, eingefroren, weigerte sich zu enden, der kleine grüne Trainingspunkt festgeklebt am oberen Bildschirmrand.
Ich stand am Straßenrand, schwitzend, tippte auf eine Watch, die nicht hören wollte, und tat das Eine, das wirklich wehtat: Ich öffnete stattdessen Apples Trainings-App und begann dort von vorn. Da war ich schon anderthalb Kilometer gelaufen. Diese Distanz ist weg. Sie hat es nie in Apple Health geschafft, sie hat es nirgendwohin geschafft, weil meine App sie nie tatsächlich aufgezeichnet hat. Ich beendete den Lauf ordentlich verärgert, was ein großzügiges Wort dafür ist.
Die App, die ich gebaut habe, damit ich nie auf die von jemand anderem zurückfallen muss, hatte mich gerade auf die von jemand anderem zurückfallen lassen. Mitten im Lauf. In der Hitze. So etwas wird in derselben Woche repariert.
Warum es nicht bis zum Herbst warten konnte
Der Teil, der das dringend statt akademisch macht: Der Bug tritt nur auf der watchOS-27-Beta auf, und ich lief auf der watchOS-27-Beta. Genauso wie überraschend viele der Leute, die Runara nutzen.
Ich glaube, ich habe das unterschätzt. Die Entwickler-und-Enthusiasten-Ecke des App Store neigt stark zu Menschen, die die Beta am Tag der Veröffentlichung auf ihre private Watch spielen, und eine datenschutzfreundliche Lauf-App zum Einmalkauf, die genau in dieser Ecke auftaucht, zieht genau diese Menschen an. Wenn im Sommer die öffentlichen Betas ausgerollt werden, läuft ein echter Teil meiner tatsächlichen Nutzer schon Monate vor dem Release auf dem Betriebssystem des nächsten Jahres. "Das funktioniert, wenn watchOS 27 im September erscheint" ist keine Antwort für jemanden, dessen Dienstagslauf gerade im Juli gescheitert ist.
Um fair zu Apple zu sein: Die Beta selbst ist gut. Für eine frühe Entwickler-Beta ist watchOS 27 auf meiner eigenen Watch bemerkenswert stabil: flüssig, keine Akku-Horrorgeschichten, nichts, das mich zurückrollen lassen wollte. Was diesen Bug umso gemeiner machte. Alles andere funktionierte, also warnte mich nichts. Das eine, das kaputtging, war genau das, wofür meine App da ist.
Einen Bug testen, den es nur in der Zukunft gibt
Das Debugging war ein eigenes kleines Martyrium, aus einem Grund, der rückwärts klingt, bis man ihn sacken lässt.
Man kann keine App ausliefern, die gegen ein Beta-SDK gebaut wurde. Der App Store akzeptiert nur Builds, die gegen das veröffentlichte SDK kompiliert wurden, für mich ist das das watchOS-26-SDK, das ausgelieferte. Die Einschränkung war also streng und leicht absurd: Ich musste einen Bug diagnostizieren, der nur auf watchOS 27 auftritt, und ihn dann in einem Binary reparieren, das gegen watchOS 26 kompiliert ist, das Betriebssystem, auf dem der Bug gar nicht passiert. Ich konnte nicht "die Beta anvisieren". Das, was ich bauen durfte, konnte das Problem nicht sehen. Das, was das Problem sehen konnte, durfte ich nicht bauen.
Das heißt, die ganze Rückkopplungsschleife ist indirekt. Auf der Beta-Watch reproduzieren. Die Console-Logs lesen, die von ihr wegströmen. Eine Theorie bilden, was das neuere OS anders macht. Code ändern, der gegen das ältere SDK kompiliert. Auf die Beta-Watch ausliefern. Nochmal laufen, in der Hitze, um zu sehen, ob es gegriffen hat. Es gibt keinen Unit-Test für "verhält sich Apples private HealthKit-Zustandsmaschine dieses Jahr anders", also war das Erste, was ich tatsächlich tat, Logging in den Trainings-Lebenszyklus zu gießen: jeder Session-Zustandswechsel, jeder Collection-Aufruf, jedes einzelne Mal, wenn der Builder mir Daten reichte, und speziell, ob er mir überhaupt jemals eine Herzfrequenz-Probe reichte. Wenn diese Zeile nie erschien, sammelte das OS keine HF, und es lag nicht an meinem Parsing. Über diesen Log-Strom habe ich es gefunden.
Was tatsächlich kaputt war
Zwei Dinge, und das erste ist fast komisch, wie klein es ist.
Wenn ein Training startet, gibt es eine Reihenfolge im HealthKit-Tanz: Man sagt dem Builder, er soll mit dem Sammeln von Daten beginnen, und man öffnet die Hauptaktivität des Trainings. Auf jedem bisherigen watchOS spielte die genaue Reihenfolge dieser beiden Aufrufe keine Rolle, und meiner öffnete zufällig zuerst die Aktivität. watchOS 27 duldet das nicht mehr. Es besteht jetzt darauf, dass das Sammeln zuerst kommt (Sammeln beginnen, dann Aktivitäten öffnen), und macht man es auf meine alte Art, wirft es "Cannot find main activity for workout" und weigert sich stillschweigend, überhaupt etwas zu sammeln. Keine Herzfrequenz. Keine Werte. Eine Session, die man nicht einmal abschließen kann. Ein umgestelltes Zeilenpaar, jahrelang unsichtbar, über Nacht tragend.
Das Zweite war der Stopp-Hänger, und er ist der Grund, warum das Training am Straßenrand nicht enden wollte. Ein Training zu beenden wartet darauf, dass HealthKit die Bücher schließt: Sammeln beenden, Training abschließen. Auf der Beta können diese Aufrufe schlicht nie zurückkehren. Mein Code wartete ohne Zeitlimit auf sie und beendete die Session erst danach, also bedeutete ein Aufruf, der nie zurückkam, eine Session, die nie endete: App im Vordergrund festgenagelt, Trainingspunkt hängen geblieben, und ein zweites Tippen auf Stopp, das ohne jede Zusammenfassung davonspaziert. Also läuft der Abschluss jetzt auf einer Stoppuhr. Er bekommt zehn Sekunden; ist HealthKit bis dahin nicht zurück, höre ich auf zu warten, erzwinge trotzdem das Ende der Session und baue die Zusammenfassungskarte aus den Zahlen, die ich ohnehin schon im Speicher habe. Du bekommst deinen Lauf trotzdem. Du bekommst dein Ergebnis trotzdem. Das OS bekommt nicht das Recht, dein Training als Geisel zu nehmen, weil ein Systemaufruf sich einen Kaffee holen ging.
Diesen Timeout habe ich in eine winzige, schlichte Funktion mit eigenen Tests gezogen, weit weg von jedem watch-only-Code, denn das Einzige, dem ich weniger traue als einer Beta, ist ein Fix, den ich nicht auf festem Boden testen kann. Und ich habe einen echten Regressionstest hinzugefügt, der eine tatsächliche Trainings-Session im Watch-Simulator startet und sicherstellt, dass der Reihenfolgen-Bug nie zurückkommen kann; er kommt weit genug, um zu beweisen, dass die Abfolge stimmt, was zugleich bewies, dass die Umstellung auf dem ausgelieferten OS völlig sicher ist, sodass niemand auf watchOS 26 für einen Fix zahlt, der auf 27 zielt.
Ab jetzt draußen
Das ist Version 1.1.1, und wenn du auf der watchOS-27-Beta bist, ist sie die, die du willst: sie bringt die Herzfrequenz und einen Stopp-Knopf, der tatsächlich stoppt, wieder dahin zurück, wo sie hingehören. Bist du auf dem ausgelieferten OS, warst du nie betroffen, und das Update ändert nichts, das dir auffallen würde, außer dass es jetzt still ein bisschen paranoider ist, jemals bei dir hängen zu bleiben.
Gleicher Deal wie immer: kein Konto, kein Abo, nichts wird gesammelt. Der Lauf bleibt an deinem Handgelenk, was mir, das verspreche ich, jetzt mehr am Herzen liegt als je zuvor.
Anderthalb Kilometer habe ich an diesen hier verloren. Du sollst das nicht müssen. Wir sehen uns auf den Trails, und wenn es bei dir so heiß ist wie hier, geh früh.
Live-Workout-Engine
Deine konfigurierten Screens erscheinen auf der Apple Watch mit Echtzeit-Werten, GPS und Haptik, auf einen Blick ablesbar unter Last.
→Herzfrequenzzonen
Zonen, verankert an deiner echten Ruhe- und Maximalherzfrequenz, mit einem farbenblind-sicheren Band, das du im Tempo ablesen kannst.
→