Was macht 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 läuft und Nebenläufigkeit explizit eingeführt wird. Beseitigt häufige Datenwettlauf-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 Datenwettlaufsicherheit
- Entwurf von MainActor-basierter App-Architektur
- Auslagern von CPU-intensiver Arbeit in Hintergrundthreads
- Implementierung von Protokollkonformitäten auf MainActor-isolierten Typen
- Aktivieren der Baueinstellungen für Zugängliche Nebenläufigkeit 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 Datenwettlauf-Fehler verursachte:
// 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: Senden von 'self.photoProcessor' riskiert Datenwettläufe
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
Swift 6.2 behebt dies: Async-Funktionen bleiben standardmäßig auf dem aufrufenden Actor.
// Swift 6.2: OK – async bleibt auf MainActor, kein Datenwettlauf
@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 nun sicher mit nicht-isolierten Protokollen konform gehen:
protocol Exportable {
func export()
}
// Swift 6.1: FEHLER – Übergang in MainActor-isolierten Code
// 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 MainActor 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 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-sendbarer Typ kann veränderlichen gemeinsamen 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-Inferenz-Modus
Swift 6.2 führt einen Modus ein, in dem MainActor standardmäßig inferiert wird – keine manuellen Annotationen erforderlich:
// Mit aktivierter MainActor-Standard-Inferenz:
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 echten Parallelismus benötigen, lagern Sie explizit mit @concurrent aus:
Wichtig: Dieses Beispiel erfordert die Baueinstellungen für Zugängliche Nebenläufigkeit – SE-0466 (MainActor-Standardisolation) und SE-0461 (NonisolatedNonsendingByDefault). Mit diesen Einstellungen bleibt
extractStickerauf dem Actor des Aufrufers, wodurch der Zugriff auf veränderlichen Zustand sicher wird. Ohne diese Einstellungen hat dieser Code einen Datenwettlauf – der Compiler wird ihn anzeigen.
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)
Um @concurrent zu verwenden:
- 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 | Der natürlichste Code ist datenwettlaufsicher; Nebenläufigkeit ist optional |
| Async bleibt auf dem aufrufenden Actor | Beseitigt implizite Auslagerungen, die Datenwettlauf-Fehler verursachten |
| Isolierte Konformitäten | MainActor-Typen können Protokollen ohne unsichere Workarounds entsprechen |
@concurrent explizite Zustimmung | Hintergrundausführung ist eine bewusste Leistungsentscheidung, kein Zufall |
| MainActor-Standard-Inferenz | Reduziert Boilerplate @MainActor-Annotationen für App-Ziele |
| Optionale Adoption | Nicht-brechender Migrationspfad – Funktionen schrittweise aktivieren |
Migrationsschritte
- In Xcode aktivieren: Swift-Compiler > Nebenläufigkeit im Bereich Baueinstellungen
- 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: Datenwettlauf-Probleme werden zu Compile-Fehlern
Best Practices
- Auf MainActor beginnen – zuerst single-threaded Code schreiben, später optimieren
@concurrentnur für CPU-intensive Arbeit verwenden – Bildverarbeitung, Kompression, komplexe Berechnungen- MainActor-Inferenzmodus aktivieren für App-Ziele, die meist 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 statt
nonisolated-Workarounds oder@Sendable-Wrapper - Inkrementell migrieren – Funktionen einzeln in den Baueinstellungen aktivieren
Anti-Patterns vermeiden
@concurrentauf jede async-Funktion anwenden (die meisten benötigen keine Hintergrundausführung)nonisolatedverwenden, um Compiler-Fehler zu unterdrücken, ohne Isolation zu verstehen- Legacy
DispatchQueue-Muster beibehalten, wenn Actors die gleiche Sicherheit bieten model.availability-Prüfungen im Nebenläufigkeits-bezogenen Foundation Models Code überspringen- Gegen den Compiler kämpfen – wenn er einen Datenwettlauf 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 Actor)
Wann verwenden
- Alle neuen Swift 6.2+ Projekte (Zugängliche Nebenläufigkeit ist der empfohlene Standard)
- Migration bestehender Apps von Swift 5.x oder 6.0/6.1 Nebenläufigkeit
- Behebung von Compiler-Fehlern zur Datenwettlaufsicherheit bei der Adoption von Xcode 26
- Entwicklung von MainActor-zentrierten App-Architekturen (die meisten UI-Apps)
- Leistungsoptimierung – Auslagern bestimmter schwerer Berechnungen in den Hintergrund