.task { } — cos'è, modificatore async e caricamento dati in View

Autore: IT Sectr Pubblicato: 2026-06-26 Tempo di lettura: 9 min

.task { } è un modificatore in SwiftUI introdotto in iOS 15 che avvia un'operazione asincrona quando una View appare e la cancella automaticamente quando la View scompare. A differenza di .onAppear, che esegue codice sincrono senza possibilità di cancellazione, .task funziona con il contesto async/await e rispetta il ciclo di vita della View: quando la View scompare, SwiftUI chiama cancel() sul Task creato. Ciò previene perdite di memoria e l'esecuzione di operazioni dopo che la View non ha più bisogno di essere aggiornata. Secondo Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024), .task è il modo preferito per caricare dati in SwiftUI perché funziona in sicurezza con Structured Concurrency e gestisce automaticamente la durata dell'operazione asincrona.

Punti Chiave

  • .task { } — modificatore SwiftUI per il caricamento asincrono di dati all'apparire di una View, disponibile da iOS 15.
  • Cancellazione automatica — quando la View scompare, SwiftUI cancella il Task, prevenendo perdite di memoria.
  • Contesto async/await — all'interno di .task, le chiamate async sono disponibili senza DispatchQueue o Combine.
  • .task(id:) — variante con identificatore che riavvia l'attività quando il valore specificato cambia.
  • Structured Concurrency — .task supporta Structured Concurrency e TaskGroup per operazioni parallele.

Cos'è .task { } in SwiftUI

.task { } è un modificatore di View che crea un Task in un contesto async quando la View appare sullo schermo. SwiftUI esegue la chiusura fornita in un thread di background, lasciando il thread principale libero per le operazioni UI. Quando la View scompare, SwiftUI cancella automaticamente il Task attraverso il meccanismo Structured Concurrency — questo garantisce che l'operazione asincrona non continui ad eseguire dopo che il suo risultato non è più necessario.

Secondo Apple — Swift Programming Language (2025), .task utilizza il concetto di Structured Concurrency introdotto in Swift 5.5. Ogni .task crea un'attività figlia nell'ambito dell'attività della View padre. Se l'attività padre viene cancellata (la View scompare), tutte le attività figlie vengono anch'esse cancellate automaticamente. Questo semplifica radicalmente la gestione del ciclo di vita delle operazioni asincrone rispetto alla memorizzazione manuale di riferimenti a DispatchWorkItem o AnyCancellable.

A differenza dell'approccio tradizionale con @State + chiamata manuale in .onAppear, .task non richiede di memorizzare un riferimento al Task per una successiva cancellazione. SwiftUI lo fa automaticamente, riducendo il codice boilerplate ed eliminando il rischio di dimenticare di cancellare un'attività.

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: differenze principali

Molti sviluppatori sono abituati a caricare dati in .onAppear, ma con l'arrivo di async/await e .task, questo approccio è diventato obsoleto. .onAppear esegue il codice in modo sincrono — per operazioni asincrone all'interno di .onAppear, è necessario avvolgere la chiamata in Task { } e mantenere manualmente un riferimento per una possibile cancellazione. .task lo fa automaticamente.

Caratteristica.task { }.onAppear
Contesto asyncAsync/await integratoRichiede wrapper Task { }
Auto-cancellazioneSì, quando la View scompareNo, deve essere implementata manualmente
Structured ConcurrencySupportaNon supporta
RiesecuzioneSolo quando id cambiaOgni volta che la View appare
Raccomandazione AppleApproccio preferitoPer operazioni sincrone

.onAppear è ancora utile per operazioni sincrone — ad esempio, logging o configurazione iniziale dell'interfaccia utente. Ma per il caricamento asincrono di dati, richieste di rete, operazioni con database o filesystem, usa .task. È più sicuro e pulito dal punto di vista architetturale.

swift
// ❌ Approccio legacy: Task in .onAppear senza cancellazione
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
    .onAppear {
        loadTask = Task { await loadData() }
    }
    .onDisappear { loadTask?.cancel() }

// ✅ Approccio moderno: .task gestisce la cancellazione
func body() { var body: some View { Text("") }
    .task { await loadData() }

.task(id:) — riavvio al cambio di dati

Il modificatore .task(id:) accetta un parametro aggiuntivo — un identificatore. Quando il valore dell'identificatore cambia, SwiftUI cancella l'attività corrente e ne avvia una nuova con il nuovo identificatore. Questo è ideale per schermate in cui i dati dipendono da un parametro selezionato — ad esempio, un elenco di articoli per categoria o dettagli di un prodotto per 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 {
            // gestisci errore
        }
    }
}

Quando categoryId cambia, SwiftUI cancella la richiesta precedente e ne avvia una nuova. Questo è particolarmente importante per cambi rapidi di categoria — le vecchie richieste non competono con quelle nuove per l'aggiornamento dello stato. Senza .task(id:), dovresti tracciare manualmente i cambiamenti tramite .onChange e gestire il Task manualmente.

Cancellazione delle attività e verifica isCancelled

Sebbene .task cancelli automaticamente l'attività quando la View scompare, l'operazione asincrona stessa deve verificare cooperativamente la cancellazione. Swift utilizza un modello di cancellazione cooperativa — Task.cancel() non interrompe forzatamente l'esecuzione, ma imposta solo il flag isCancelled. Il codice all'interno dell'attività dovrebbe verificare periodicamente questo flag.

swift
struct LoadingView: View {
    @State var progress: Double = 0
    
    var body: some View {
        ProgressView(value: progress)
            .task {
                for i in 0..<100 {
                    // Verifica cancellazione
                    try Task.checkCancellation()
                    
                    await Task.sleep(nanoseconds: 50_000_000)
                    progress = Double(i + 1) / 100.0
                }
            }
    }
}

Task.checkCancellation() lancia CancellationError se l'attività è stata cancellata. Questo è il modo più semplice di verifica — funziona in qualsiasi contesto async. Un'alternativa è verificare Task.isCancelled manualmente prima di operazioni costose. Per URLSession, le richieste di rete vengono automaticamente cancellate quando l'attività viene cancellata, poiché URLSession supporta Structured Concurrency nativamente.

Esempi pratici di .task

.task è adatto a molti scenari: dal semplice caricamento JSON a complesse operazioni parallele con TaskGroup. Esaminiamo tre casi d'uso tipici.

Caricamento con gestione degli errori

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("Caricamento fallito")
            }
        }
        .task {
            defer { isLoading = false }
            do {
                profile = await APIClient().fetchProfile()
            } catch {
                // profilo rimane nil
            }
        }
    }
}

Caricamento parallelo con TaskGroup

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

Errori comuni con .task

L'errore più comune è mutare proprietà UI all'interno di .task senza passare al thread principale. Sebbene SwiftUI restituisca automaticamente gli aggiornamenti al thread principale quando si modifica @State in un contesto async, manipolazioni dirette di elementi UIKit all'interno di .task possono causare un crash.

  • Dimenticare try/catch — .task non gestisce automaticamente gli errori. Tutte le funzioni che lanciano errori devono essere avvolte in do/catch, altrimenti l'app si bloccherà.
  • Race condition — se più .task(id:) vengono avviati con id diversi e aggiornano lo stesso stato, possono verificarsi race condition. Usa proprietà separate per diverse fonti di dati.
  • Operazioni sincrone lunghe — .task non rende asincrono il codice sincrono. Se c'è lavoro sincrono pesante all'interno di .task, avvolgilo in Task.detached o spostalo in un metodo async separato.
  • Ignorare CancellationError — quando si verifica Task.checkCancellation(), CancellationError dovrebbe essere propagato verso l'alto, non soppresso. Sopprimere la cancellazione può portare a perdite di memoria.
swift
// ❌ Errore: nessuna gestione errori
.task {
    let data = await fetchData() // si blocca in caso di errore!
    items = data
}

// ✅ Corretto: do/catch
.task {
    do {
        items = await fetchData()
    } catch {
        errorMessage = error.localizedDescription
    }
}

Domande Frequenti

Qual è la differenza tra .task e l'uso di Task { } in .onAppear?

.task cancella automaticamente l'attività quando la View scompare e supporta Structured Concurrency. Task { } in .onAppear richiede di mantenere manualmente un riferimento all'attività e chiamare cancel() in .onDisappear. .task è anche più facile da leggere — indica esplicitamente che il caricamento dei dati fa parte del ciclo di vita della View.

Si può usare .task per iscriversi a un publisher?

Sì, per iscriversi a un AsyncSequence o AsyncStream, usa for await value in publisher.values all'interno di .task. Funziona sia con async/await che con Combine tramite l'estensione Publisher.values. Quando la View scompare, l'iterazione terminerà automaticamente e l'iscrizione verrà cancellata.

Come funziona .task con TabView — l'attività si riavvia cambiando scheda?

Sì, se la View all'interno di TabView viene ricreata ad ogni cambio. A partire da iOS 18, TabView può mantenere le Views in memoria — in questo caso .task non si riavvia. Usa .task(id:) con un identificatore di scheda se devi ricaricare i dati ad ogni cambio di scheda.

Cosa succede se una View con .task scompare prima del completamento della richiesta?

SwiftUI cancellerà l'attività quando la View scompare. Se la richiesta URLSession era all'interno dell'attività, verrà anch'essa cancellata. Se l'attività non supporta la cancellazione cooperativa (ad esempio, non verifica isCancelled), continuerà a eseguire, ma il suo risultato non verrà applicato allo stato perché la View non esiste più.

Si possono usare più modificatori .task su una singola View?

Sì, puoi aggiungere più modificatori .task su una singola View. Ognuno crea un'attività indipendente. Questo è utile per separare diverse fonti di dati: un .task per caricare un profilo, un secondo per iscriversi a un WebSocket, un terzo per monitorare la geolocalizzazione.

Riepilogo

  • .task { } — modificatore SwiftUI per operazioni asincrone con auto-cancellazione quando la View scompare.
  • Supporto async/await — all'interno di .task, un contesto async completo è disponibile senza wrapper Task.
  • .task(id:) — riavvia l'attività quando l'identificatore cambia, sostituendo .onChange manuale.
  • Cancellazione cooperativa — usa Task.checkCancellation() per verificare la cancellazione all'interno dell'attività.
  • Structured Concurrency — .task supporta TaskGroup e operazioni parallele con cancellazione delle attività figlie.
  • Sostituto di .onAppear — per il caricamento asincrono di dati, usa .task invece del pattern .onAppear + Task + .onDisappear.
  • Gestione errori — tutte le chiamate async all'interno di .task devono essere avvolte in do/catch per prevenire crash.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche