.task { } ist ein in iOS 15 eingeführter SwiftUI-Modifikator, der eine asynchrone Operation startet, wenn eine View erscheint, und sie automatisch abbricht, wenn die View verschwindet. Im Gegensatz zu .onAppear, das synchronen Code ohne Abbruchmöglichkeit ausführt, arbeitet .task mit dem async/await-Kontext und berücksichtigt den Lebenszyklus der View: Wenn die View verschwindet, ruft SwiftUI cancel() auf dem erstellten Task auf. Dies verhindert Speicherlecks und die Ausführung von Operationen, nachdem die View nicht mehr aktualisiert werden muss. Laut Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024) ist .task die bevorzugte Methode zum Laden von Daten in SwiftUI, da es sicher mit Structured Concurrency arbeitet und die Lebensdauer der asynchronen Operation automatisch verwaltet.
Wichtige Punkte
.task { } ist ein View-Modifikator, der einen Task in einem async-Kontext erstellt, wenn die View auf dem Bildschirm erscheint. SwiftUI führt den übergebenen Closure im Hintergrundthread aus und lässt den Hauptthread für UI-Operationen frei. Wenn die View verschwindet, bricht SwiftUI den Task automatisch über den Structured-Concurrency-Mechanismus ab — dies stellt sicher, dass die asynchrone Operation nicht weiterläuft, nachdem ihr Ergebnis nicht mehr benötigt wird.
Laut Apple — Swift Programming Language (2025) verwendet .task das Konzept der Structured Concurrency, das in Swift 5.5 eingeführt wurde. Jeder .task erstellt einen Kind-Task innerhalb des Tasks der Eltern-View. Wenn der Eltern-Task abgebrochen wird (die View verschwindet), werden alle Kind-Tasks ebenfalls automatisch abgebrochen. Dies vereinfacht die Verwaltung des Lebenszyklus asynchroner Operationen im Vergleich zum manuellen Speichern von Referenzen auf DispatchWorkItem oder AnyCancellable radikal.
Im Gegensatz zum traditionellen Ansatz mit @State + manuellem Aufruf in .onAppear erfordert .task keine Speicherung einer Referenz auf den Task für den späteren Abbruch. SwiftUI erledigt dies automatisch, reduziert Boilerplate-Code und eliminiert das Risiko, einen Task-Abbruch zu vergessen.
struct ArticlesView: View {
@State var articles: [Article] = []
@State var error: Error?
var body: some View {
List(articles) { article in
Text(article.title)
}
.task {
do {
articles = await APIClient().fetchArticles()
} catch {
self.error = error
}
}
}
}
Viele Entwickler sind es gewohnt, Daten in .onAppear zu laden, aber mit dem Aufkommen von async/await und .task ist dieser Ansatz veraltet. .onAppear führt Code synchron aus — für asynchrone Operationen innerhalb von .onAppear muss der Aufruf in Task { } gewrappt und manuell eine Referenz für den möglichen Abbruch vorgehalten werden. .task erledigt dies automatisch.
| Eigenschaft | .task { } | .onAppear |
|---|---|---|
| Async-Kontext | Integriertes async/await | Benötigt Task { }-Wrapper |
| Auto-Abbruch | Ja, beim Verschwinden der View | Nein, muss manuell implementiert werden |
| Structured Concurrency | Unterstützt | Nicht unterstützt |
| Neuausführung | Nur bei Änderung von id | Jedes Mal beim Erscheinen der View |
| Apple-Empfehlung | Bevorzugte Methode | Für synchrone Operationen |
.onAppear ist weiterhin nützlich für synchrone Operationen — zum Beispiel Logging oder initiale UI-Einrichtung. Aber für asynchrones Datenladen, Netzwerkanfragen, Datenbank- oder Dateisystemoperationen verwenden Sie .task. Es ist sicherer und architektonisch sauberer.
// ❌ Legacy-Ansatz: Task in .onAppear ohne Abbruch
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
.onAppear {
loadTask = Task { await loadData() }
}
.onDisappear { loadTask?.cancel() }
// ✅ Moderner Ansatz: .task verwaltet Abbruch
func body() { var body: some View { Text("") }
.task { await loadData() }
Der .task(id:)-Modifikator akzeptiert einen zusätzlichen Parameter — einen Identifikator. Wenn sich der Wert des Identifikators ändert, bricht SwiftUI den aktuellen Task ab und startet einen neuen mit dem neuen Identifikator. Dies ist ideal für Bildschirme, bei denen Daten von einem ausgewählten Parameter abhängen — zum Beispiel eine Artikelliste nach Kategorie oder Produktdetails nach ID.
struct CategoryView: View {
let categoryId: Int
@State var items: [Item] = []
var body: some View {
List(items) { item in
Text(item.name)
}
.task(id: categoryId) {
await loadItems(for: categoryId)
}
}
func loadItems(for id: Int) async {
do {
items = await APIClient().fetchItems(categoryId: id)
} catch {
// Fehler behandeln
}
}
}
Wenn sich categoryId ändert, bricht SwiftUI die vorherige Anfrage ab und startet eine neue. Dies ist besonders wichtig bei schnellen Kategoriewechseln — alte Anfragen konkurrieren nicht mit neuen um die Statusaktualisierung. Ohne .task(id:) müssten Sie Änderungen manuell über .onChange verfolgen und den Task manuell verwalten.
Obwohl .task den Task beim Verschwinden der View automatisch abbricht, muss die asynchrone Operation selbst kooperativ den Abbruch prüfen. Swift verwendet ein kooperatives Abbruchmodell — Task.cancel() stoppt die Ausführung nicht zwangsweise, sondern setzt lediglich das Flag isCancelled. Der Code innerhalb des Tasks sollte dieses Flag regelmäßig prüfen.
struct LoadingView: View {
@State var progress: Double = 0
var body: some View {
ProgressView(value: progress)
.task {
for i in 0..<100 {
// Abbruch prüfen
try Task.checkCancellation()
await Task.sleep(nanoseconds: 50_000_000)
progress = Double(i + 1) / 100.0
}
}
}
}
Task.checkCancellation() wirft CancellationError, wenn der Task abgebrochen wurde. Dies ist die einfachste Prüfmethode — sie funktioniert in jedem async-Kontext. Eine Alternative ist die manuelle Prüfung von Task.isCancelled vor aufwändigen Operationen. Bei URLSession werden Netzwerkanfragen automatisch abgebrochen, wenn der Task abgebrochen wird, da URLSession Structured Concurrency nativ unterstützt.
.task eignet sich für viele Szenarien: vom einfachen JSON-Laden bis zu komplexen parallelen Operationen mit TaskGroup. Betrachten wir drei typische Anwendungsfälle.
struct ProfileView: View {
@State var profile: Profile?
@State var isLoading = true
var body: some View {
Group {
if isLoading {
ProgressView()
} else if let profile {
Text(profile.name)
} else {
Text("Laden fehlgeschlagen")
}
}
.task {
defer { isLoading = false }
do {
profile = await APIClient().fetchProfile()
} catch {
// Profil bleibt nil
}
}
}
}
struct DashboardView: View {
@State var stats: DashboardStats?
var body: some View {
Text("Dashboard")
.task {
stats = await Task {
await withThrowingTaskGroup { group in
group.addTask { await API().fetchUsers() }
group.addTask { await API().fetchOrders() }
group.addTask { await API().fetchRevenue() }
return DashboardStats(
users: try await group.next(),
orders: try await group.next(),
revenue: try await group.next()
)
}
}.value
}
}
}
Der häufigste Fehler ist das Mutieren von UI-Eigenschaften innerhalb von .task ohne Umschalten auf den Hauptthread. Obwohl SwiftUI Aktualisierungen automatisch an den Hauptthread zurückgibt, wenn @State in einem async-Kontext geändert wird, können direkte Manipulationen von UIKit-Elementen innerhalb von .task einen Absturz verursachen.
// ❌ Fehler: keine Fehlerbehandlung
.task {
let data = await fetchData() // stürzt bei Fehler ab!
items = data
}
// ✅ Richtig: do/catch
.task {
do {
items = await fetchData()
} catch {
errorMessage = error.localizedDescription
}
}
Häufig gestellte Fragen
.task bricht den Task automatisch ab, wenn die View verschwindet, und unterstützt Structured Concurrency. Task { } in .onAppear erfordert das manuelle Vorhalten einer Referenz auf den Task und den Aufruf von cancel() in .onDisappear. .task ist auch leichter zu lesen — es zeigt explizit an, dass das Datenladen Teil des View-Lebenszyklus ist.
Ja, zum Abonnieren eines AsyncSequence oder AsyncStream verwenden Sie for await value in publisher.values innerhalb von .task. Dies funktioniert sowohl mit async/await als auch mit Combine über die Publisher.values-Erweiterung. Wenn die View verschwindet, endet die Iteration automatisch und das Abonnement wird gekündigt.
Ja, wenn die View innerhalb von TabView bei jedem Wechsel neu erstellt wird. Ab iOS 18 kann TabView Views im Speicher halten — in diesem Fall wird .task nicht neu gestartet. Verwenden Sie .task(id:) mit einem Tab-Identifikator, wenn Sie bei jedem Tab-Wechsel Daten neu laden müssen.
SwiftUI bricht den Task ab, wenn die View verschwindet. Wenn die URLSession-Anfrage innerhalb des Tasks war, wird auch sie abgebrochen. Wenn der Task keinen kooperativen Abbruch unterstützt (z. B. isCancelled nicht prüft), läuft er weiter, aber sein Ergebnis wird nicht auf den Status angewendet, da die View nicht mehr existiert.
Ja, Sie können mehrere .task-Modifikatoren auf einer einzigen View hinzufügen. Jeder erstellt einen unabhängigen Task. Dies ist nützlich zur Trennung verschiedener Datenquellen: ein .task zum Laden eines Profils, ein zweiter zum Abonnieren eines WebSocket, ein dritter zur Überwachung des Standorts.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch