Swift 6.2 Zugängliche Nebenläufigkeit
Muster für die Übernahme des Nebenläufigkeitsmodells von Swift 6.2, bei dem Code standardmäßig single-threaded ausgeführt wird und Nebenläufigkeit explizit eingeführt wird. Beseitigt häufige Data-Race-Fehler ohne Leistungseinbußen.
Wann aktivieren
- Migration von Swift 5.x oder 6.0/6.1 Projekten zu Swift 6.2
- Behebung von Compiler-Fehlern zur Data-Race-Sicherheit
- Entwurf einer MainActor-basierten App-Architektur
- Auslagern CPU-intensiver Arbeit in Hintergrundthreads
- Implementierung von Protokollkonformitäten auf MainActor-isolierten Typen
- Aktivieren der Build-Einstellungen „Approachable Concurrency“ in Xcode 26
Kernproblem: Implizites Auslagern in den Hintergrund
In Swift 6.1 und früher konnten asynchrone Funktionen implizit in Hintergrundthreads ausgelagert werden, was selbst in scheinbar sicherem Code zu Data-Race-Fehlern führte:
// Swift 6.1: FEHLER
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
// Fehler: Das Senden von 'self.photoProcessor' birgt das Risiko von Datenkonflikten
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
Swift 6.2 behebt dies: Asynchrone Funktionen bleiben standardmäßig auf dem aufrufenden Akteur.
// Swift 6.2: OK — async bleibt auf MainActor, kein Data Race
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
Kernmuster — Isolierte Konformitäten
MainActor-Typen können jetzt sicher mit nicht-isolierten Protokollen konform gehen:
protocol Exportable {
func export()
}
// Swift 6.1: FEHLER — greift auf MainActor-isolierten Code zu
// Swift 6.2: OK mit isolierter Konformität
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
Der Compiler stellt sicher, dass die Konformität nur auf dem Main-Actor verwendet wird:
// OK — ImageExporter ist ebenfalls @MainActor
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Sicher: gleiche Actor-Isolation
}
}
// FEHLER — nicht-isolierter Kontext kann die MainActor-Konformität nicht verwenden
nonisolated struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Fehler: MainActor-isolierte Konformität kann hier nicht verwendet werden
}
}
Kernmuster — Globale und statische Variablen
Schützen Sie globalen/statischen Zustand mit MainActor:
// Swift 6.1: FEHLER — Nicht-Sendable-Typ kann gemeinsam genutzten veränderlichen Zustand haben
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Fehler
}
// Lösung: Mit @MainActor annotieren
@MainActor
final class StickerLibrary {
static let shared: StickerLibrary = .init() // OK
}
MainActor-Standard-Inferenzmodus
Swift 6.2 führt einen Modus ein, bei dem MainActor standardmäßig inferiert wird – keine manuellen Annotationen erforderlich:
// Mit aktiviertem MainActor-Standard-Inferenzmodus:
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Implizit @MainActor
}
final class StickerModel {
let photoProcessor: PhotoProcessor
var selection: [PhotosPickerItem] // Implizit @MainActor
}
extension StickerModel: Exportable { // Implizit @MainActor-Konformität
func export() {
photoProcessor.exportAsPNG()
}
}
Dieser Modus ist optional und wird für Apps, Skripte und andere ausführbare Ziele empfohlen.
Kernmuster — @concurrent für Hintergrundarbeit
Wenn Sie echte Parallelität benötigen, lagern Sie explizit mit @concurrent aus:
Wichtig: Dieses Beispiel erfordert die Build-Einstellungen für „Approachable Concurrency“ – SE-0466 (MainActor-Standard-Isolation) und SE-0461 (NonisolatedNonsendingByDefault). Wenn diese aktiviert sind, bleibt
extractStickerauf dem Akteur des Aufrufers, was den Zugriff auf veränderlichen Zustand sicher macht. Ohne diese Einstellungen hat dieser Code einen Data Race – der Compiler wird ihn markieren.
nonisolated final class PhotoProcessor {
private var cachedStickers: [String: Sticker] = [:]
func extractSticker(data: Data, with id: String) async -> Sticker {
if let sticker = cachedStickers[id] {
return sticker
}
let sticker = await Self.extractSubject(from: data)
cachedStickers[id] = sticker
return sticker
}
// Teure Arbeit in den gleichzeitigen Thread-Pool auslagern
@concurrent
static func extractSubject(from data: Data) async -> Sticker { /* ... */ }
}
// Aufrufer müssen await verwenden
let processor = PhotoProcessor()
processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)
So verwenden Sie @concurrent:
- Markieren Sie den enthaltenden Typ als
nonisolated - Fügen Sie
@concurrentzur Funktion hinzu - Fügen Sie
asynchinzu, falls nicht bereits asynchron - Fügen Sie
awaitan den Aufrufstellen hinzu
Wichtige Designentscheidungen
| Entscheidung | Begründung |
|---|---|
| Standardmäßig single-threaded | Natürlichster Code ist data-race-frei; Nebenläufigkeit ist optional |
| Async bleibt auf dem aufrufenden Akteur | Beseitigt implizites Auslagern, das Data-Race-Fehler verursachte |
| Isolierte Konformitäten | MainActor-Typen können ohne unsichere Workarounds mit Protokollen konform gehen |
@concurrent explizites Opt-in | Hintergrundausführung ist eine bewusste Leistungsentscheidung, kein Zufall |
| MainActor-Standard-Inferenz | Reduziert Boilerplate-@MainActor-Annotationen für App-Ziele |
| Optionales Opt-in | Nicht-brechender Migrationspfad – Funktionen schrittweise aktivieren |
Migrationsschritte
- In Xcode aktivieren: Swift-Compiler > Nebenläufigkeitsbereich in den Build-Einstellungen
- In SPM aktivieren:
SwiftSettings-API im Paketmanifest verwenden - Migrationstools verwenden: Automatische Codeänderungen über swift.org/migration
- Mit MainActor-Standards beginnen: Inferenzmodus für App-Ziele aktivieren
@concurrentwo nötig hinzufügen: Zuerst profilieren, dann heiße Pfade auslagern- Gründlich testen: Data-Race-Probleme werden zu Compile-Zeit-Fehlern
Best Practices
- Auf MainActor starten – zuerst single-threaded Code schreiben, später optimieren
@concurrentnur für CPU-intensive Arbeit verwenden – Bildverarbeitung, Komprimierung, komplexe Berechnungen- MainActor-Inferenzmodus aktivieren für App-Ziele, die größtenteils single-threaded sind
- Vor dem Auslagern profilieren – Instruments verwenden, um tatsächliche Engpässe zu finden
- Globale mit MainActor schützen – globaler/statischer veränderlicher Zustand benötigt Actor-Isolation
- Isolierte Konformitäten verwenden anstelle von
nonisolated-Workarounds oder@Sendable-Wrapper - Inkrementell migrieren – Funktionen einzeln in den Build-Einstellungen aktivieren
Anti-Patterns vermeiden
@concurrentauf jede async-Funktion anwenden (die meisten benötigen keine Hintergrundausführung)nonisolatedverwenden, um Compiler-Fehler zu unterdrücken, ohne die Isolation zu verstehen- Legacy
DispatchQueue-Muster beibehalten, wenn Actors die gleiche Sicherheit bieten model.availability-Prüfungen in Nebenläufigkeits-bezogenem Foundation Models-Code überspringen- Gegen den Compiler kämpfen – wenn er einen Data Race meldet, hat der Code ein echtes Nebenläufigkeitsproblem
- Annehmen, dass aller async-Code im Hintergrund läuft (Swift 6.2 Standard: bleibt auf dem aufrufenden Akteur)
Wann verwenden
- Alle neuen Swift 6.2+ Projekte (Approachable Concurrency ist die empfohlene Standardeinstellung)
- Migration bestehender Apps von Swift 5.x oder 6.0/6.1 Nebenläufigkeit
- Behebung von Data-Race-Sicherheits-Compiler-Fehlern während der Xcode 26 Übernahme
- Erstellung MainActor-zentrierter App-Architekturen (die meisten UI-Apps)
- Leistungsoptimierung – Auslagern spezifischer schwerer Berechnungen in den Hintergrund