.task { } — je modifikátor ve SwiftUI, představený v iOS 15, který spouští asynchronní operaci při objevení View a automaticky ji ruší při zmizení. Na rozdíl od .onAppear, který provádí synchronní kód bez možnosti zrušení, .task pracuje s kontextem async/await a bere v úvahu životní cyklus View: při zmizení View SwiftUI volá cancel() na vytvořeném Task. Tím se předchází únikům paměti a provádění operací poté, co View již nepotřebuje aktualizaci. Podle Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024) je .task preferovaným způsobem načítání dat ve SwiftUI, protože bezpečně pracuje se Structured Concurrency a automaticky spravuje dobu života asynchronní operace.
Hlavní body
.task { } — je modifikátor View, který vytváří Task v kontextu async při objevení View na obrazovce. SwiftUI spouští předané closure na pozadí, zatímco hlavní vlákno zůstává volné pro UI operace. Když View zmizí, SwiftUI automaticky ruší Task prostřednictvím mechanismu Structured Concurrency — to zaručuje, že asynchronní operace nebude pokračovat poté, co její výsledek již není nikomu potřebný.
Podle Apple — Swift Programming Language (2025) používá .task koncept Structured Concurrency, zavedený ve Swift 5.5. Každý .task vytváří podřízenou úlohu v rámci úlohy nadřazeného View. Pokud je nadřazená úloha zrušena (View zmizí), všechny podřízené úlohy jsou také automaticky zrušeny. To radikálně zjednodušuje správu životního cyklu asynchronních operací ve srovnání s ručním ukládáním referencí na DispatchWorkItem nebo AnyCancellable.
Na rozdíl od tradičního přístupu s @State + ručním voláním v .onAppear, .task nevyžaduje ukládání reference na Task pro pozdější zrušení. SwiftUI to dělá automaticky, což snižuje množství boilerplate kódu a eliminuje riziko zapomenutí zrušit úlohu.
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
}
}
}
}
Mnoho vývojářů je zvyklých načítat data v .onAppear, ale s příchodem async/await a .task je tento přístup zastaralý. .onAppear provádí kód synchronně — pro asynchronní operace uvnitř .onAppear je třeba zabalit volání do Task { } a ručně ukládat referenci na něj pro případné zrušení. .task to dělá automaticky.
| Vlastnost | .task { } | .onAppear |
|---|---|---|
| Async kontext | Vestavěný async/await | Vyžaduje obalení Task { } |
| Automatické zrušení | Ano, při zmizení View | Ne, je třeba implementovat ručně |
| Structured Concurrency | Podporuje | Nepodporuje |
| Restart | Pouze při změně id | Pokaždé při objevení |
| Doporučení Apple | Preferovaný způsob | Pro synchronní operace |
.onAppear je stále užitečný pro synchronní operace — například logování nebo počáteční konfiguraci UI. Ale pro asynchronní načítání dat, síťové požadavky, práci s databází nebo souborovým systémem používej .task. Je to bezpečnější a čistší z architektonického hlediska.
// ❌ Starý přístup: Task v .onAppear bez zrušení
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
.onAppear {
loadTask = Task { await loadData() }
}
.onDisappear { loadTask?.cancel() }
// ✅ Moderní přístup: .task spravuje zrušení
func body() { var body: some View { Text("") }
.task { await loadData() }
Modifikátor .task(id:) přijímá další parametr — identifikátor. Když se hodnota identifikátoru změní, SwiftUI zruší aktuální úlohu a spustí novou s novým identifikátorem. To je ideální pro obrazovky, kde data závisí na vybraném parametru — například seznam článků podle kategorie nebo detaily produktu podle 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 {
// ošetři chybu
}
}
}
Když se categoryId změní, SwiftUI zruší předchozí požadavek a spustí nový. To je důležité zejména při rychlém přepínání kategorií — staré požadavky nebudou soutěžit s novými o aktualizaci stavu. Bez .task(id:) byste museli ručně sledovat změny přes .onChange a ručně spravovat Task.
Ačkoli .task automaticky ruší úlohu při zmizení View, samotná asynchronní operace musí kooperativně kontrolovat zrušení. Swift používá kooperativní model rušení — Task.cancel() nezastavuje provádění násilně, ale pouze nastavuje příznak isCancelled. Kód uvnitř úlohy by měl tento příznak pravidelně kontrolovat.
struct LoadingView: View {
@State var progress: Double = 0
var body: some View {
ProgressView(value: progress)
.task {
for i in 0..<100 {
// Zkontrolovat zrušení
try Task.checkCancellation()
await Task.sleep(nanoseconds: 50_000_000)
progress = Double(i + 1) / 100.0
}
}
}
}
Task.checkCancellation() vyhazuje CancellationError, pokud byla úloha zrušena. Toto je nejjednodušší způsob kontroly — funguje v jakémkoli async kontextu. Alternativa — ruční kontrola Task.isCancelled před nákladnými operacemi. Pro URLSession jsou síťové požadavky automaticky zrušeny při zrušení úlohy, protože URLSession podporuje Structured Concurrency od základu.
.task je vhodný pro mnoho scénářů: od jednoduchého načítání JSON po složité paralelní operace s TaskGroup. Podívejme se na tři typické příklady použití.
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("Načtení selhalo")
}
}
.task {
defer { isLoading = false }
do {
profile = await APIClient().fetchProfile()
} catch {
// profil zůstává nil
}
}
}
}
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
}
}
}
Nejčastější chyba — mutace UI vlastností uvnitř .task bez přepnutí na hlavní vlákno. Ačkoli SwiftUI automaticky vrací aktualizace na hlavní vlákno při změně @State v async kontextu, přímé manipulace s UIKit prvky uvnitř .task mohou způsobit crash.
// ❌ Chyba: žádné ošetření chyb
.task {
let data = await fetchData() // při chybě spadne!
items = data
}
// ✅ Správně: do/catch
.task {
do {
items = await fetchData()
} catch {
errorMessage = error.localizedDescription
}
}
Často kladené otázky
.task automaticky ruší úlohu při zmizení View a podporuje Structured Concurrency. Task { } v .onAppear vyžaduje ruční ukládání reference na úlohu a volání cancel() v .onDisappear. .task je také snadněji čitelný — explicitně uvádí, že načítání dat je součástí životního cyklu View.
Ano, pro přihlášení k odběru AsyncSequence nebo AsyncStream použij for await value in publisher.values uvnitř .task. Funguje to jak s async/await, tak s Combine přes rozšíření Publisher.values. Při zmizení View iterace automaticky skončí a odběr bude zrušen.
Ano, pokud je View uvnitř TabView při každém přepnutí znovu vytvořeno. Od iOS 18 může TabView uchovávat View v paměti — v tomto případě se .task nerestartuje. Použij .task(id:) s identifikátorem karty, pokud potřebuješ znovu načítat data při každém přepnutí.
SwiftUI zruší úlohu při zmizení View. Pokud byl požadavek URLSession uvnitř úlohy, bude také zrušen. Pokud úloha nepodporuje kooperativní zrušení (např. nekontroluje isCancelled), bude pokračovat v provádění, ale její výsledek nebude aplikován na stav, protože View již neexistuje.
Ano, na jedno View lze přidat několik modifikátorů .task. Každý vytváří nezávislou úlohu. To je vhodné pro oddělení různých zdrojů dat: jeden .task pro načítání profilu, druhý pro přihlášení k WebSocket, třetí pro sledování geolokace.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také