.task { } — qu'est-ce que c'est, modificateur async et chargement de données dans View

Auteur : IT Sectr Publié le : 2026-06-26 Temps de lecture : 9 min

.task { } est un modificateur dans SwiftUI introduit dans iOS 15 qui lance une opération asynchrone lorsqu'une View apparaît et l'annule automatiquement lorsqu'elle disparaît. Contrairement à .onAppear, qui exécute du code synchrone sans possibilité d'annulation, .task fonctionne avec le contexte async/await et respecte le cycle de vie de la View : quand la View disparaît, SwiftUI appelle cancel() sur la Task créée. Cela évite les fuites mémoire et l'exécution d'opérations après que la View n'a plus besoin d'être mise à jour. Selon Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024), .task est la méthode préférée pour charger des données dans SwiftUI car il fonctionne en toute sécurité avec Structured Concurrency et gère automatiquement la durée de vie de l'opération asynchrone.

Points Clés

  • .task { } — modificateur SwiftUI pour le chargement asynchrone de données à l'apparition d'une View, disponible depuis iOS 15.
  • Annulation automatique — quand la View disparaît, SwiftUI annule la Task, évitant les fuites mémoire.
  • Contexte async/await — à l'intérieur de .task, les appels async sont disponibles sans DispatchQueue ni Combine.
  • .task(id:) — variante avec identifiant qui redémarre la tâche lorsque la valeur spécifiée change.
  • Structured Concurrency — .task prend en charge Structured Concurrency et TaskGroup pour les opérations parallèles.

Qu'est-ce que .task { } dans SwiftUI

.task { } est un modificateur de View qui crée une Task dans un contexte async lorsque la View apparaît à l'écran. SwiftUI exécute la fermeture fournie dans un thread d'arrière-plan, laissant le thread principal libre pour les opérations d'interface utilisateur. Lorsque la View disparaît, SwiftUI annule automatiquement la Task via le mécanisme Structured Concurrency — cela garantit que l'opération asynchrone ne continue pas à s'exécuter après que son résultat n'est plus nécessaire.

Selon Apple — Swift Programming Language (2025), .task utilise le concept de Structured Concurrency introduit dans Swift 5.5. Chaque .task crée une tâche enfant dans le cadre de la tâche de la View parent. Si la tâche parent est annulée (la View disparaît), toutes les tâches enfants sont également annulées automatiquement. Cela simplifie radicalement la gestion du cycle de vie des opérations asynchrones par rapport au stockage manuel de références à DispatchWorkItem ou AnyCancellable.

Contrairement à l'approche traditionnelle avec @State + appel manuel dans .onAppear, .task ne nécessite pas de stocker une référence à la Task pour une annulation ultérieure. SwiftUI le fait automatiquement, réduisant le code boilerplate et éliminant le risque d'oublier d'annuler une tâche.

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 : différences clés

De nombreux développeurs ont l'habitude de charger des données dans .onAppear, mais avec l'arrivée d'async/await et de .task, cette approche est devenue obsolète. .onAppear exécute le code de manière synchrone — pour les opérations asynchrones dans .onAppear, il faut envelopper l'appel dans Task { } et conserver manuellement une référence pour une éventuelle annulation. .task le fait automatiquement.

Caractéristique.task { }.onAppear
Contexte asyncAsync/await intégréNécessite un wrapper Task { }
Auto-annulationOui, quand la View disparaîtNon, doit être implémenté manuellement
Structured ConcurrencyPris en chargeNon pris en charge
RéexécutionSeulement quand id changeChaque fois que la View apparaît
Recommandation AppleApproche préféréePour les opérations synchrones

.onAppear est toujours utile pour les opérations synchrones — par exemple, la journalisation ou la configuration initiale de l'interface utilisateur. Mais pour le chargement asynchrone de données, les requêtes réseau, les opérations de base de données ou de système de fichiers, utilisez .task. C'est plus sûr et plus propre d'un point de vue architectural.

swift
// ❌ Approche legacy : Task dans .onAppear sans annulation
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
    .onAppear {
        loadTask = Task { await loadData() }
    }
    .onDisappear { loadTask?.cancel() }

// ✅ Approche moderne : .task gère l'annulation
func body() { var body: some View { Text("") }
    .task { await loadData() }

.task(id:) — redémarrage lors du changement de données

Le modificateur .task(id:) accepte un paramètre supplémentaire — un identifiant. Lorsque la valeur de l'identifiant change, SwiftUI annule la tâche actuelle et en démarre une nouvelle avec le nouvel identifiant. C'est idéal pour les écrans où les données dépendent d'un paramètre sélectionné — par exemple, une liste d'articles par catégorie ou les détails d'un produit par 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 {
            // gérer l'erreur
        }
    }
}

Lorsque categoryId change, SwiftUI annule la requête précédente et en lance une nouvelle. C'est particulièrement important pour les changements rapides de catégorie — les anciennes requêtes ne feront pas concurrence aux nouvelles pour la mise à jour de l'état. Sans .task(id:), vous devriez suivre manuellement les changements via .onChange et gérer la Task manuellement.

Annulation des tâches et vérification isCancelled

Bien que .task annule automatiquement la tâche lorsque la View disparaît, l'opération asynchrone elle-même doit vérifier coopérativement l'annulation. Swift utilise un modèle d'annulation coopérative — Task.cancel() n'arrête pas l'exécution de force, mais définit seulement le drapeau isCancelled. Le code à l'intérieur de la tâche doit vérifier périodiquement ce drapeau.

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

Task.checkCancellation() lance CancellationError si la tâche a été annulée. C'est la méthode de vérification la plus simple — elle fonctionne dans n'importe quel contexte async. Une alternative est de vérifier Task.isCancelled manuellement avant les opérations coûteuses. Pour URLSession, les requêtes réseau sont automatiquement annulées lorsque la tâche est annulée, car URLSession prend en charge Structured Concurrency nativement.

Exemples pratiques de .task

.task convient à de nombreux scénarios : du simple chargement JSON aux opérations parallèles complexes avec TaskGroup. Examinons trois cas d'utilisation typiques.

Chargement avec gestion d'erreurs

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("Échec du chargement")
            }
        }
        .task {
            defer { isLoading = false }
            do {
                profile = await APIClient().fetchProfile()
            } catch {
                // le profil reste nil
            }
        }
    }
}

Chargement parallèle avec TaskGroup

swift
struct DashboardView: View {
    @State var stats: DashboardStats?
    
    var body: some View {
        Text("Tableau 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
            }
    }
}

Erreurs courantes avec .task

L'erreur la plus courante est de muter des propriétés d'interface utilisateur à l'intérieur de .task sans passer au thread principal. Bien que SwiftUI retourne automatiquement les mises à jour au thread principal lors de la modification de @State dans un contexte async, les manipulations directes d'éléments UIKit à l'intérieur de .task peuvent provoquer un crash.

  • Oubli de try/catch — .task ne gère pas les erreurs automatiquement. Toutes les fonctions qui lancent des erreurs doivent être enveloppées dans do/catch, sinon l'application plantera.
  • Condition de concurrence — si plusieurs .task(id:) sont lancés avec des identifiants différents et mettent à jour le même état, des conditions de concurrence peuvent se produire. Utilisez des propriétés séparées pour différentes sources de données.
  • Opérations synchrones longues — .task ne rend pas le code synchrone asynchrone. S'il y a un travail synchrone lourd à l'intérieur de .task, enveloppez-le dans Task.detached ou déplacez-le dans une méthode async séparée.
  • Ignorer CancellationError — lors de la vérification de Task.checkCancellation(), CancellationError doit être propagé vers le haut, pas supprimé. Supprimer l'annulation peut entraîner des fuites mémoire.
swift
// ❌ Erreur : pas de gestion d'erreurs
.task {
    let data = await fetchData() // plante en cas d'erreur !
    items = data
}

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

Questions Fréquentes

Quelle est la différence entre .task et l'utilisation de Task { } dans .onAppear ?

.task annule automatiquement la tâche lorsque la View disparaît et prend en charge Structured Concurrency. Task { } dans .onAppear nécessite de conserver manuellement une référence à la tâche et d'appeler cancel() dans .onDisappear. .task est également plus facile à lire — il indique explicitement que le chargement des données fait partie du cycle de vie de la View.

Peut-on utiliser .task pour s'abonner à un publisher ?

Oui, pour s'abonner à un AsyncSequence ou AsyncStream, utilisez for await value in publisher.values à l'intérieur de .task. Cela fonctionne à la fois avec async/await et avec Combine via l'extension Publisher.values. Lorsque la View disparaît, l'itération se termine automatiquement et l'abonnement est annulé.

Comment .task fonctionne-t-il avec TabView — la tâche redémarre-t-elle lors du changement d'onglet ?

Oui, si la View à l'intérieur de TabView est recréée à chaque changement. À partir d'iOS 18, TabView peut conserver les Views en mémoire — dans ce cas, .task ne redémarre pas. Utilisez .task(id:) avec un identifiant d'onglet si vous devez recharger les données à chaque changement d'onglet.

Que se passe-t-il si une View avec .task disparaît avant la fin de la requête ?

SwiftUI annulera la tâche lorsque la View disparaîtra. Si la requête URLSession était à l'intérieur de la tâche, elle sera également annulée. Si la tâche ne prend pas en charge l'annulation coopérative (par exemple, elle ne vérifie pas isCancelled), elle continuera à s'exécuter, mais son résultat ne sera pas appliqué à l'état car la View n'existe plus.

Peut-on utiliser plusieurs modificateurs .task sur une seule View ?

Oui, vous pouvez ajouter plusieurs modificateurs .task sur une seule View. Chacun crée une tâche indépendante. C'est utile pour séparer différentes sources de données : un .task pour charger un profil, un deuxième pour s'abonner à un WebSocket, un troisième pour surveiller la géolocalisation.

Résumé

  • .task { } — modificateur SwiftUI pour les opérations asynchrones avec annulation automatique quand la View disparaît.
  • Prise en charge async/await — à l'intérieur de .task, un contexte async complet est disponible sans wrapper Task.
  • .task(id:) — redémarre la tâche lorsque l'identifiant change, remplaçant .onChange manuel.
  • Annulation coopérative — utilisez Task.checkCancellation() pour vérifier l'annulation à l'intérieur de la tâche.
  • Structured Concurrency — .task prend en charge TaskGroup et les opérations parallèles avec annulation des tâches enfants.
  • Remplacement de .onAppear — pour le chargement asynchrone de données, utilisez .task au lieu du pattern .onAppear + Task + .onDisappear.
  • Gestion d'erreurs — tous les appels async à l'intérieur de .task doivent être enveloppés dans do/catch pour éviter les crashes.

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi