.task { } — is een modifier in SwiftUI, geïntroduceerd in iOS 15, die een asynchrone bewerking start wanneer een View verschijnt en deze automatisch annuleert wanneer deze verdwijnt. In tegenstelling tot .onAppear, die synchrone code uitvoert zonder annuleringsmogelijkheid, werkt .task met een async/await context en houdt rekening met de levenscyclus van de View: wanneer de View verdwijnt, roept SwiftUI cancel() aan op de aangemaakte Task. Dit voorkomt geheugenlekken en het uitvoeren van bewerkingen nadat de View niet meer hoeft te worden bijgewerkt. Volgens Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024) is .task de voorkeursmethode voor het laden van gegevens in SwiftUI, omdat het veilig samenwerkt met Structured Concurrency en automatisch de levensduur van de asynchrone bewerking beheert.
Belangrijkste punten
.task { } — is een View modifier die een Task aanmaakt in async context wanneer de View op het scherm verschijnt. SwiftUI start de doorgegeven closure op de achtergrond, terwijl de hoofdthread vrij blijft voor UI-bewerkingen. Wanneer de View verdwijnt, annuleert SwiftUI automatisch de Task via het Structured Concurrency mechanisme — dit garandeert dat de asynchrone bewerking niet doorgaat nadat het resultaat ervan niet meer nodig is.
Volgens Apple — Swift Programming Language (2025) gebruikt .task het Structured Concurrency concept, geïntroduceerd in Swift 5.5. Elke .task maakt een subtask aan binnen de taak van de bovenliggende View. Als de bovenliggende taak wordt geannuleerd (View verdwijnt), worden alle subtaken ook automatisch geannuleerd. Dit vereenvoudigt het beheer van de levenscyclus van asynchrone bewerkingen radicaal in vergelijking met het handmatig bijhouden van verwijzingen naar DispatchWorkItem of AnyCancellable.
In tegenstelling tot de traditionele aanpak met @State + handmatige aanroep in .onAppear, vereist .task niet dat je een verwijzing naar de Task bewaart voor latere annulering. SwiftUI doet dit automatisch, wat de hoeveelheid boilerplate code vermindert en het risico elimineert om te vergeten een taak te annuleren.
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
}
}
}
}
Veel ontwikkelaars zijn gewend om gegevens te laden in .onAppear, maar met de komst van async/await en .task is deze aanpak verouderd. .onAppear voert code synchroon uit — voor asynchrone bewerkingen binnen .onAppear moet je de aanroep in Task { } wrappen en handmatig een verwijzing ernaar bewaren voor mogelijke annulering. .task doet dit automatisch.
| Kenmerk | .task { } | .onAppear |
|---|---|---|
| Async context | Ingebouwde async/await | Vereist Task { } wrapper |
| Auto-annuleren | Ja, bij verdwijnen van View | Nee, moet handmatig worden geïmplementeerd |
| Structured Concurrency | Ondersteunt | Ondersteunt niet |
| Herstarten | Alleen bij wijziging van id | Elke keer bij verschijnen |
| Aanbeveling Apple | Voorkeursmethode | Voor synchrone bewerkingen |
.onAppear is nog steeds nuttig voor synchrone bewerkingen — bijvoorbeeld loggen of initiële UI-configuratie. Maar voor asynchroon laden van gegevens, netwerkverzoeken, werken met databases of bestandssystemen, gebruik .task. Dit is veiliger en schoner vanuit architectonisch oogpunt.
// ❌ Legacy aanpak: Task in .onAppear zonder annulering
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
.onAppear {
loadTask = Task { await loadData() }
}
.onDisappear { loadTask?.cancel() }
// ✅ Moderne aanpak: .task beheert annulering
func body() { var body: some View { Text("") }
.task { await loadData() }
De modifier .task(id:) accepteert een extra parameter — een identifier. Wanneer de waarde van de identifier verandert, annuleert SwiftUI de huidige taak en start een nieuwe met de nieuwe identifier. Dit is ideaal voor schermen waar gegevens afhankelijk zijn van een geselecteerde parameter — bijvoorbeeld een lijst met artikelen per categorie of productdetails op 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 {
// fout afhandelen
}
}
}
Wanneer categoryId verandert, annuleert SwiftUI het vorige verzoek en start een nieuw. Dit is vooral belangrijk bij snel schakelen tussen categorieën — oude verzoeken zullen niet concurreren met nieuwe voor het bijwerken van de status. Zonder .task(id:) zou je wijzigingen handmatig moeten volgen via .onChange en de Task handmatig moeten beheren.
Hoewel .task de taak automatisch annuleert wanneer de View verdwijnt, moet de asynchrone bewerking zelf coöperatief annulering controleren. Swift gebruikt een coöperatief annuleringsmodel — Task.cancel() stopt de uitvoering niet geforceerd, maar stelt alleen de isCancelled-vlag in. Code binnen de taak moet deze vlag periodiek controleren.
struct LoadingView: View {
@State var progress: Double = 0
var body: some View {
ProgressView(value: progress)
.task {
for i in 0..<100 {
// Controleer annulering
try Task.checkCancellation()
await Task.sleep(nanoseconds: 50_000_000)
progress = Double(i + 1) / 100.0
}
}
}
}
Task.checkCancellation() gooit CancellationError als de taak is geannuleerd. Dit is de eenvoudigste controlemethode — het werkt in elke async context. Alternatief — handmatig controleren van Task.isCancelled voor dure bewerkingen. Voor URLSession worden netwerkverzoeken automatisch geannuleerd bij annulering van de taak, omdat URLSession Structured Concurrency vanaf het begin ondersteunt.
.task is geschikt voor vele scenario's: van eenvoudig JSON laden tot complexe parallelle bewerkingen met TaskGroup. Laten we drie typische gebruiksvoorbeelden bekijken.
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 mislukt")
}
}
.task {
defer { isLoading = false }
do {
profile = await APIClient().fetchProfile()
} catch {
// profiel blijft 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
}
}
}
De meest voorkomende fout — het muteren van UI-eigenschappen binnen .task zonder naar de hoofdthread te schakelen. Hoewel SwiftUI automatisch updates naar de hoofdthread terugstuurt bij wijziging van @State in async context, kunnen directe manipulaties van UIKit-elementen binnen .task een crash veroorzaken.
// ❌ Fout: geen foutafhandeling
.task {
let data = await fetchData() // crasht bij fout!
items = data
}
// ✅ Correct: do/catch
.task {
do {
items = await fetchData()
} catch {
errorMessage = error.localizedDescription
}
}
Veelgestelde vragen
.task annuleert de taak automatisch wanneer de View verdwijnt en ondersteunt Structured Concurrency. Task { } in .onAppear vereist het handmatig bewaren van een verwijzing naar de taak en het aanroepen van cancel() in .onDisappear. .task is ook makkelijker te lezen — het geeft expliciet aan dat het laden van gegevens deel uitmaakt van de levenscyclus van de View.
Ja, om je te abonneren op AsyncSequence of AsyncStream gebruik je for await value in publisher.values binnen .task. Dit werkt zowel met async/await als met Combine via de Publisher.values extensie. Wanneer de View verdwijnt, stopt de iteratie automatisch en wordt het abonnement geannuleerd.
Ja, als de View binnen TabView bij elke schakeling opnieuw wordt aangemaakt. Vanaf iOS 18 kan TabView de View in het geheugen bewaren — in dat geval wordt .task niet opnieuw gestart. Gebruik .task(id:) met de tabblad-identifier als je gegevens opnieuw moet laden bij elke schakeling.
SwiftUI annuleert de taak wanneer de View verdwijnt. Als het URLSession-verzoek binnen de taak was, wordt het ook geannuleerd. Als de taak geen coöperatieve annulering ondersteunt (bijvoorbeeld geen isCancelled controleert), gaat de uitvoering door, maar het resultaat wordt niet toegepast op de status omdat de View niet meer bestaat.
Ja, je kunt meerdere .task modifiers aan één View toevoegen. Elk maakt een onafhankelijke taak aan. Dit is handig voor het scheiden van verschillende gegevensbronnen: één .task voor het laden van het profiel, een tweede voor abonneren op WebSocket, een derde voor het monitoren van geolocatie.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook