.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 { } 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.
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
}
}
}
}
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 async | Async/await intégré | Nécessite un wrapper Task { } |
| Auto-annulation | Oui, quand la View disparaît | Non, doit être implémenté manuellement |
| Structured Concurrency | Pris en charge | Non pris en charge |
| Réexécution | Seulement quand id change | Chaque fois que la View apparaît |
| Recommandation Apple | Approche préférée | Pour 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.
// ❌ 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() }
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.
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.
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.
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.
.task convient à de nombreux scénarios : du simple chargement JSON aux opérations parallèles complexes avec TaskGroup. Examinons trois cas d'utilisation typiques.
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
}
}
}
}
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
}
}
}
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.
// ❌ 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
.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.
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é.
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.
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.
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é
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.
Lisez aussi