Die Ankündigung von Swift 6 klang verlockend: Endlich sollte der Compiler Nebenläufigkeitsfehler zur Compile-Zeit aufspüren und Programmierer vor den berüchtigten Heisenbugs bewahren. Doch der Umstieg auf das neue Sprachfeature erwies sich als weitaus komplexer als erwartet – besonders, wenn veraltete Abhängigkeiten den Weg versperrten.
Dass eine Migration zu Swift 6 nicht mit einem einfachen Schalter umzulegen ist, musste ein Entwickler am eigenen Leib erfahren. Sein Ziel war klar: Die SwiftUI-basierte Debug- und Abfragesoftware Ditto Edge Studio sollte von den neuen Sicherheitsgarantien profitieren. Doch schon der erste Schritt zeigte, wie tiefgreifend die Änderungen waren. Die Liste der Compiler-Warnungen war länger als erwartet – und jede einzelne ein Hinweis auf potenzielle Datenrennen oder Isolationsprobleme.
Warum eine einzige veraltete Abhängigkeit alles blockieren kann
Der erste Stolperstein war kein Algorithmus, kein komplexer Sync-Mechanismus – sondern ein scheinbar harmloses Paket. Ein beliebter SwiftUI-Codeeditor für die DQL-Abfragesprache zog transitive Abhängigkeiten nach sich, die mit Swift 6 nicht kompatibel waren. Beide Pakete – Editor und Syntax-Highlighting-Bibliothek – waren zwar funktional perfekt, aber in einer Ära geschrieben, die noch keine strenge Nebenläufigkeitsprüfung kannte.
Die naheliegenden Lösungen schienen verlockend einfach:
- Die Abhängigkeit einfach auf einer älteren Swift-Version einfrieren und das Risiko künftiger Nebenläufigkeitsfehler in Kauf nehmen.
- Den Code selbst forken und die Anpassungen vornehmen – ein Schritt, der jedoch bedeutet hätte, die Wartung eines Fremdprojekts zu übernehmen.
- Die Abhängigkeit komplett entfernen und durch eine Swift-6-kompatible Alternative ersetzen.
Am Ende fiel die Entscheidung auf die radikalste Option: Die Abhängigkeit wurde entfernt. Als Ersatz diente das Paket HighlightSwift für das Syntax-Highlighting, kombiniert mit einem selbst entwickelten DQLCodeEditor. Die Implementierung nutzte @MainActor und asynchrone Tasks, um die UI-Interaktionen sicher zu isolieren. Ein zentraler Bestandteil war die Coordinator-Klasse, die für die verzögerte Syntax-Hervorhebung zuständig war – und dabei auf Swift 6.2’s isolierte Deinitialisierer setzte, um Ressourcen sicher freizugeben.
Die Lehre daraus ist klar: In einem Swift-6-Projekt ist die Abhängigkeitskette Teil der Nebenläufigkeitsstrategie. Bevor der Compiler überhaupt zum Einsatz kommt, sollte eine gründliche Prüfung aller Pakete erfolgen. Andernfalls droht die unangenehme Überraschung, dass ein harmloses Drittanbieter-Paket aus dem Jahr 2021 die gesamte Migration blockiert.
44 Warnungen und die unerbittliche Logik des Compilers
Mit der Entfernung des problematischen Pakets war der Weg für die eigentliche Migration frei. Die Projektdatei wurde angepasst, um Swift 6 und die vollständige Nebenläufigkeitsprüfung zu aktivieren:
SWIFT_VERSION = 6.0
SWIFT_STRICT_CONCURRENCY = completeDoch statt eines sauberen Builds lieferte der Compiler eine Liste mit 44 Warnungen – jede ein Hinweis auf Isolationsprobleme, veraltete API-Aufrufe oder nicht korrekt annotierte Nebenläufigkeitsbereiche. Die Warnungen ließen sich in vier Hauptkategorien einteilen:
- UI-Frameworks ohne klare Isolationsannotationen: SpriteKit-Szenen, AppKit-Views und AVFoundation-Komponenten, die implizit den Hauptthread nutzten, mussten nun explizit als
@MainActormarkiert werden. - Veraltete String-Konstruktoren: Methoden wie
String(cString:)waren endlich nicht mehr toleriert, da sie unsichere Nebenläufigkeitsannahmen trafen. - Eigenen Code, der während der Migration entstanden war und ebenfalls Isolationsprobleme aufwies.
- Testumgebung: Helferfunktionen, die mit
@MainActorannotiert waren, wurden aus nicht-isolierten Testkörpern aufgerufen – ein Muster, das auf beiden Plattformen (macOS und iPad) korrigiert werden musste.
Der Weg zur Fehlerfreiheit war kein Sprint, sondern ein Marathon. Jede Warnung erforderte eine systematische Analyse: Welche Isolationsstufe ist hier tatsächlich erforderlich, und wie lässt sich das dem Compiler klar kommunizieren? Besonders knifflig gestaltete sich die Anpassung der plattformspezifischen Unterschiede zwischen macOS und iPad, die in manchen Fällen zu überraschenden API-Verfügbarkeitsproblemen führten.
Das Vokabular der Nebenläufigkeit: Was Entwickler wirklich wissen müssen
Nach Hunderten von Warnungen begann der Entwickler, die neuen Konzepte nicht mehr als Syntax, sondern als Ausdruck von Absicht zu lesen. Einige Schlüsselbegriffe erwiesen sich als besonders praxisrelevant:
@MainActor – mehr als nur ein Annotationstrick
Die meisten UI-Komponenten – View-Modelle, UI-Glue-Code und alle Bereiche, die AppKit oder UIKit nutzten – waren schnell mit @MainActor annotiert. Doch die wahren Herausforderungen lagen in den Nuancen. So waren etwa SpriteKit-Szenen und deren Layer bereits vom SDK mit @MainActor versehen. Das bedeutete, dass viele selbstgeschriebenen DispatchQueue.main.async-Aufrufe überflüssig wurden – und oft sogar schädlich, weil sie unnötige Thread-Hops verursachten.
Ein konkretes Beispiel:
// Vor Swift 6: Ein GCD-Hop, geschrieben aus Gewohnheit
DispatchQueue.main.async { [weak self] in
self?.onZoomChanged?(newScale)
}In Swift 6 konnte dieser Code oft stark vereinfacht werden:
// Nach Swift 6: Direkter Zugriff, da der Aufruf bereits @MainActor ist
onZoomChanged?(newScale)Isolierte Deinitialisierer und sichere Ressourcenfreigabe
Swift 6.2 führte isolierte Deinitialisierer ein, die es ermöglichten, Tasks sicher auf dem Haupt-Actor abzubrechen, ohne auf veraltete Muster wie das Erfassen von self in einem losgelösten Task zurückgreifen zu müssen. Ein typischer Anwendungsfall war die Bereinigung von Hintergrund-Tasks:
isolated deinit {
highlightTask?.cancel()
}Diese Konstruktion stellte sicher, dass Ressourcen auch dann korrekt freigegeben wurden, wenn der Task noch lief – und das alles ohne zusätzliche Synchronisationsmechanismen.
Fazit: Eine Migration, die sich lohnt – aber nicht unterschätzt werden darf
Die Umstellung auf Swift 6 war kein Spaziergang. Sie erforderte Geduld, eine gründliche Planung und die Bereitschaft, alte Gewohnheiten zu hinterfragen. Doch der Aufwand hat sich gelohnt: Der Compiler deckte Nebenläufigkeitsprobleme auf, die in Jahren des Betriebs unbemerkt geblieben wären. Die App profitiert nun von einer robusteren Architektur, die weniger anfällig für schwer reproduzierbare Fehler ist.
Für Entwickler, die ähnliche Schritte planen, gilt: Beginne mit der Abhängigkeitsanalyse. Prüfe, ob alle Pakete Swift 6 unterstützen, und bereite dich auf eine Phase intensiver Code-Refaktorierung vor. Die Belohnung ist eine Codebasis, die nicht nur funktioniert, sondern auch nachweislich sicher in Bezug auf Nebenläufigkeit ist.
KI-Zusammenfassung
Learn how migrating a production SwiftUI app to Swift 6’s strict concurrency mode exposed 44 warnings, evicted a legacy dependency, and enforced compile-time safety.