.task { } — este un modificator în SwiftUI, introdus în iOS 15, care lansează o operație asincronă la apariția View și o anulează automat la dispariție. Spre deosebire de .onAppear, care execută cod sincron fără posibilitatea de anulare, .task funcționează cu contextul async/await și ține cont de ciclul de viață al View: la dispariția View, SwiftUI apelează cancel() pe Task-ul creat. Acest lucru previne scurgerile de memorie și executarea operațiilor după ce View nu mai are nevoie de actualizare. Conform Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024), .task este metoda preferată de încărcare a datelor în SwiftUI, deoarece funcționează sigur cu Structured Concurrency și gestionează automat durata de viață a operației asincrone.
Principalele puncte
.task { } — este un modificator View care creează un Task în context async la apariția View pe ecran. SwiftUI lansează closure-ul transmis în fundal, în timp ce firul principal rămâne liber pentru operații UI. Când View dispare, SwiftUI anulează automat Task-ul prin mecanismul Structured Concurrency — aceasta garantează că operația asincronă nu va continua după ce rezultatul ei nu mai este necesar nimănui.
Conform Apple — Swift Programming Language (2025), .task folosește conceptul de Structured Concurrency, introdus în Swift 5.5. Fiecare .task creează o sarcină copil în cadrul sarcinii părinte View. Dacă sarcina părinte este anulată (View dispare), toate sarcinile copil sunt de asemenea anulate automat. Aceasta simplifică radical gestionarea ciclului de viață al operațiilor asincrone în comparație cu păstrarea manuală a referințelor la DispatchWorkItem sau AnyCancellable.
Spre deosebire de abordarea tradițională cu @State + apel manual în .onAppear, .task nu necesită păstrarea unei referințe la Task pentru anulare ulterioară. SwiftUI face acest lucru automat, ceea ce reduce cantitatea de cod boilerplate și elimină riscul de a uita să anulezi sarcina.
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
}
}
}
}
Mulți dezvoltatori sunt obișnuiți să încarce date în .onAppear, dar odată cu apariția async/await și .task această abordare este depășită. .onAppear execută cod sincron — pentru operații asincrone în interiorul .onAppear trebuie să împachetezi apelul în Task { } și să păstrezi manual o referință la el pentru o posibilă anulare. .task face acest lucru automat.
| Caracteristică | .task { } | .onAppear |
|---|---|---|
| Context async | async/await încorporat | Necesită împachetare Task { } |
| Autoanulare | Da, la dispariția View | Nu, trebuie implementată manual |
| Structured Concurrency | Suportă | Nu suportă |
| Repornire | Doar la modificarea id | De fiecare dată la apariție |
| Recomandare Apple | Metoda preferată | Pentru operații sincrone |
.onAppear este încă util pentru operații sincrone — de exemplu, logare sau configurarea inițială a UI. Dar pentru încărcarea asincronă a datelor, cereri de rețea, lucrul cu baza de date sau sistemul de fișiere, folosește .task. Este mai sigur și mai curat din punct de vedere arhitectural.
// ❌ Abordare veche: Task în .onAppear fără anulare
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
.onAppear {
loadTask = Task { await loadData() }
}
.onDisappear { loadTask?.cancel() }
// ✅ Abordare modernă: .task gestionează anularea
func body() { var body: some View { Text("") }
.task { await loadData() }
Modificatorul .task(id:) primește un parametru suplimentar — un identificator. Când valoarea identificatorului se modifică, SwiftUI anulează sarcina curentă și lansează una nouă cu noul identificator. Este ideal pentru ecranele unde datele depind de un parametru selectat — de exemplu, lista de articole pe categorii sau detaliile unui produs după 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 {
// gestionează eroarea
}
}
}
Când categoryId se modifică, SwiftUI anulează cererea anterioară și lansează una nouă. Acest lucru este deosebit de important la comutarea rapidă între categorii — cererile vechi nu vor concura cu cele noi pentru actualizarea stării. Fără .task(id:) ar trebui să urmărești manual modificările prin .onChange și să gestionezi manual Task.
Deși .task anulează automat sarcina la dispariția View, operația asincronă în sine trebuie să verifice cooperant anularea. Swift folosește un model de anulare cooperant — Task.cancel() nu oprește execuția forțat, ci doar setează flag-ul isCancelled. Codul din interiorul sarcinii trebuie să verifice periodic acest flag.
struct LoadingView: View {
@State var progress: Double = 0
var body: some View {
ProgressView(value: progress)
.task {
for i in 0..<100 {
// Verifică anularea
try Task.checkCancellation()
await Task.sleep(nanoseconds: 50_000_000)
progress = Double(i + 1) / 100.0
}
}
}
}
Task.checkCancellation() aruncă CancellationError dacă sarcina a fost anulată. Aceasta este cea mai simplă metodă de verificare — funcționează în orice context async. Alternativa — verificarea manuală a Task.isCancelled înainte de operații costisitoare. Pentru URLSession, cererile de rețea sunt anulate automat la anularea sarcinii, deoarece URLSession suportă Structured Concurrency din start.
.task este potrivit pentru multe scenarii: de la încărcarea simplă a JSON la operații paralele complexe cu TaskGroup. Să analizăm trei exemple tipice de utilizare.
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("Încărcare eșuată")
}
}
.task {
defer { isLoading = false }
do {
profile = await APIClient().fetchProfile()
} catch {
// profilul rămâne nil
}
}
}
}
struct DashboardView: View {
@State var stats: DashboardStats?
var body: some View {
Text("Tablou de bord")
.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
}
}
}
Cea mai frecventă eroare — mutarea proprietăților UI în interiorul .task fără comutarea pe firul principal. Deși SwiftUI returnează automat actualizările pe firul principal la modificarea @State în context async, manipulările directe ale elementelor UIKit în interiorul .task pot cauza un crash.
// ❌ Eroare: fără gestionarea erorilor
.task {
let data = await fetchData() // se prăbușește la eroare!
items = data
}
// ✅ Corect: do/catch
.task {
do {
items = await fetchData()
} catch {
errorMessage = error.localizedDescription
}
}
Întrebări frecvente
.task anulează automat sarcina la dispariția View și suportă Structured Concurrency. Task { } în .onAppear necesită păstrarea manuală a unei referințe la sarcină și apelarea cancel() în .onDisappear. .task este, de asemenea, mai ușor de citit — indică explicit că încărcarea datelor face parte din ciclul de viață al View.
Da, pentru abonarea la AsyncSequence sau AsyncStream folosește for await value in publisher.values în interiorul .task. Funcționează atât cu async/await, cât și cu Combine prin extensia Publisher.values. La dispariția View, iterația se va încheia automat, iar abonamentul va fi anulat.
Da, dacă View-ul din interiorul TabView este recreat la fiecare comutare. Începând cu iOS 18, TabView poate păstra View-ul în memorie — în acest caz, .task nu se repornește. Folosește .task(id:) cu identificatorul filei dacă trebuie să reîncarci datele la fiecare comutare.
SwiftUI anulează sarcina la dispariția View. Dacă cererea URLSession era în interiorul sarcinii, va fi de asemenea anulată. Dacă sarcina nu suportă anularea cooperantă (de exemplu, nu verifică isCancelled), va continua execuția, dar rezultatul său nu va fi aplicat stării, deoarece View-ul nu mai există.
Da, poți adăuga mai mulți modificatori .task pe același View. Fiecare creează o sarcină independentă. Este util pentru separarea diferitelor surse de date: un .task pentru încărcarea profilului, al doilea pentru abonarea la WebSocket, al treilea pentru monitorizarea geolocației.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și