.task { } — co to je, modifikátor async a načítání dat ve View

Autor: IT Sectr Publikováno: 2026-06-26 Doba čtení: 9 min

.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 { } — modifikátor SwiftUI pro asynchronní načítání dat při objevení View, dostupný od iOS 15.
  • Automatické zrušení — při zmizení View SwiftUI ruší Task, čímž předchází únikům paměti.
  • Kontext async/await — uvnitř .task jsou dostupná asynchronní volání bez potřeby DispatchQueue nebo Combine.
  • .task(id:) — varianta s identifikátorem restartuje úlohu při změně zadané hodnoty.
  • Structured Concurrency — .task podporuje Structured Concurrency a TaskGroup pro paralelní operace.

Co je .task { } ve SwiftUI

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

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: klíčové rozdíly

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 kontextVestavěný async/awaitVyžaduje obalení Task { }
Automatické zrušeníAno, při zmizení ViewNe, je třeba implementovat ručně
Structured ConcurrencyPodporujeNepodporuje
RestartPouze při změně idPokaždé při objevení
Doporučení ApplePreferovaný způsobPro 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.

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

.task(id:) — restart při změně dat

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.

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

Rušení úloh a kontrola isCancelled

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.

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

Praktické příklady .task

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

Načítání s ošetřením chyb

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("Načtení selhalo")
            }
        }
        .task {
            defer { isLoading = false }
            do {
                profile = await APIClient().fetchProfile()
            } catch {
                // profil zůstává nil
            }
        }
    }
}

Paralelní načítání s 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
            }
    }
}

Typické chyby s .task

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.

  • Zapomněl jsi try/catch — .task nezpracovává chyby automaticky. Všechny funkce vyhazující chyby uvnitř musí být zabaleny do do/catch, jinak aplikace spadne.
  • Race condition — pokud je několik .task(id:) spuštěno s různými id a aktualizuje stejný stav, jsou možné race condition. Používej oddělené vlastnosti pro různé zdroje dat.
  • Dlouhé synchronní operace — .task nedělá synchronní kód asynchronním. Pokud je uvnitř .task těžká synchronní práce, zabal ji do Task.detached nebo ji přesuň do samostatné async metody.
  • Ignorování CancellationError — při kontrole Task.checkCancellation() by měla být chyba CancellationError propagována nahoru, nikoli potlačena. Potlačení zrušení může vést k únikům paměti.
swift
// ❌ 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

Jaký je rozdíl mezi .task a použitím Task { } v .onAppear?

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

Lze .task použít pro přihlášení k odběru publisheru?

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.

Jak .task funguje s TabView — restartuje se úloha při přepínání karet?

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

Co se stane, když View s .task zmizí před dokončením požadavku?

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.

Lze použít více .task na jednom View?

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í

  • .task { } — modifikátor SwiftUI pro asynchronní operace s automatickým zrušením při zmizení View.
  • Podpora async/await — uvnitř .task je k dispozici plný async kontext bez potřeby obalení Task.
  • .task(id:) — restartuje úlohu při změně identifikátoru, nahrazuje ruční .onChange.
  • Kooperativní zrušení — použij Task.checkCancellation() pro kontrolu zrušení uvnitř úlohy.
  • Structured Concurrency — .task podporuje TaskGroup a paralelní operace se zrušením podřízených úloh.
  • Náhrada .onAppear — pro asynchronní načítání dat použij .task místo kombinace .onAppear + Task + .onDisappear.
  • Ošetření chyb — všechna async volání uvnitř .task musí být zabalena v do/catch, aby se předešlo pádu.

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

Prodiskutovat projekt

Přečtěte si také