.task { } — ce este, modificatorul async și încărcarea datelor în View

Autor: IT Sectr Publicat: 2026-06-26 Timp de citire: 9 min

.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 { } — modificator SwiftUI pentru încărcarea asincronă a datelor la apariția View, disponibil din iOS 15.
  • Anulare automată — la dispariția View, SwiftUI anulează Task, prevenind scurgerile de memorie.
  • Context async/await — în interiorul .task sunt disponibile apeluri async fără necesitatea DispatchQueue sau Combine.
  • .task(id:) — varianta cu identificator repornește sarcina la modificarea valorii specificate.
  • Structured Concurrency — .task suportă Structured Concurrency și TaskGroup pentru operații paralele.

Ce este .task { } în SwiftUI

.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.

swift
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
            }
        }
    }
}

.task vs .onAppear: diferențe cheie

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 asyncasync/await încorporatNecesită împachetare Task { }
AutoanulareDa, la dispariția ViewNu, trebuie implementată manual
Structured ConcurrencySuportăNu suportă
RepornireDoar la modificarea idDe fiecare dată la apariție
Recomandare AppleMetoda 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.

swift
// ❌ 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() }

.task(id:) — repornire la modificarea datelor

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.

swift
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.

Anularea sarcinilor și verificarea isCancelled

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.

swift
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.

Exemple practice .task

.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.

Încărcare cu gestionarea erorilor

swift
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
            }
        }
    }
}

Încărcare paralelă cu TaskGroup

swift
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
            }
    }
}

Erori tipice cu .task

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.

  • Ai uitat try/catch — .task nu gestionează erorile automat. Toate funcțiile care aruncă erori în interior trebuie împachetate în do/catch, altfel aplicația se va prăbuși.
  • Race condition — dacă mai multe .task(id:) sunt lansate cu id-uri diferite și actualizează aceeași stare, sunt posibile condiții de cursă. Folosește proprietăți separate pentru diferite surse de date.
  • Operații sincrone lungi — .task nu face codul sincron asincron. Dacă în interiorul .task există o muncă sincronă grea, împacheteaz-o în Task.detached sau mut-o într-o metodă async separată.
  • Ignorarea CancellationError — la verificarea Task.checkCancellation(), eroarea CancellationError trebuie propagată în sus, nu suprimată. Suprimarea anulării poate duce la scurgeri de memorie.
swift
// ❌ 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

Care este diferența dintre .task și utilizarea Task { } în .onAppear?

.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.

Se poate folosi .task pentru abonarea la un publisher?

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.

Cum funcționează .task cu TabView — sarcina se repornește la comutarea filelor?

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.

Ce se întâmplă dacă View-ul cu .task dispare înainte de finalizarea cererii?

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ă.

Se pot folosi multiple .task pe același View?

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

  • .task { } — modificator SwiftUI pentru operații asincrone cu anulare automată la dispariția View.
  • Suport async/await — în interiorul .task este disponibil un context async complet fără a fi nevoie de împachetare în Task.
  • .task(id:) — repornește sarcina la modificarea identificatorului, înlocuind .onChange manual.
  • Anulare cooperantă — folosește Task.checkCancellation() pentru a verifica anularea în interiorul sarcinii.
  • Structured Concurrency — .task suportă TaskGroup și operații paralele cu anularea sarcinilor copil.
  • Înlocuirea .onAppear — pentru încărcarea asincronă a datelor folosește .task în locul combinației .onAppear + Task + .onDisappear.
  • Gestionarea erorilor — toate apelurile async din interiorul .task trebuie împachetate în do/catch pentru a preveni crash-ul.

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.

Discutați proiectul

Citiți și