.task { } — је модификатор у SwiftUI, представљен у iOS 15, који покреће асинхрону операцију при појављивању View и аутоматски је отказује при нестанку. За разлику од .onAppear, који извршава синхрони код без могућности отказивања, .task ради са async/await контекстом и узима у обзир животни циклус View: при нестанку View SwiftUI позива cancel() на креираном Task-у. Ово спречава цурење меморије и извршавање операција након што View више не треба ажурирати. Према Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024), .task је пожељан начин учитавања података у SwiftUI, јер безбедно ради са Structured Concurrency и аутоматски управља животним веком асинхроне операције.
Главне тачке
.task { } — је модификатор View који креира Task у async контексту при појављивању View на екрану. SwiftUI покреће прослеђени closure у позадини, док главна нит остаје слободна за UI операције. Када View нестане, SwiftUI аутоматски отказује Task кроз механизам Structured Concurrency — ово гарантује да се асинхрона операција неће наставити након што њен резултат више никоме није потребан.
Према Apple — Swift Programming Language (2025), .task користи концепт Structured Concurrency, уведен у Swift 5.5. Сваки .task креира подређени задатак у оквиру задатка родитељског View. Ако је родитељски задатак отказан (View нестане), сви подређени задаци се такође аутоматски отказују. Ово радикално поједностављује управљање животним циклусом асинхроних операција у поређењу са ручним чувањем референци на DispatchWorkItem или AnyCancellable.
За разлику од традиционалног приступа са @State + ручним позивом у .onAppear, .task не захтева чување референце на Task за касније отказивање. SwiftUI то ради аутоматски, што смањује количину boilerplate кода и елиминише ризик заборава отказивања задатка.
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
}
}
}
}
Многи програмери су навикли да учитавају податке у .onAppear, али са појавом async/await и .task овај приступ је застарео. .onAppear извршава код синхроно — за асинхроне операције унутар .onAppear потребно је умотати позив у Task { } и ручно чувати референцу на њега за евентуално отказивање. .task то ради аутоматски.
| Карактеристика | .task { } | .onAppear |
|---|---|---|
| Async контекст | Уграђени async/await | Захтева Task { } омотач |
| Аутоотказивање | Да, при нестанку View | Не, потребно ручно имплементирати |
| Structured Concurrency | Подржава | Не подржава |
| Поновно покретање | Само при промени id | Сваки пут при појављивању |
| Препорука Apple | Пожељан начин | За синхроне операције |
.onAppear је и даље користан за синхроне операције — на пример, логирање или почетно подешавање UI. Али за асинхроно учитавање података, мрежне захтеве, рад са базом података или системом датотека користи .task. Ово је сигурније и чистије са архитектуралне тачке гледишта.
// ❌ Стари приступ: Task у .onAppear без отказивања
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
.onAppear {
loadTask = Task { await loadData() }
}
.onDisappear { loadTask?.cancel() }
// ✅ Модерни приступ: .task управља отказивањем
func body() { var body: some View { Text("") }
.task { await loadData() }
Модификатор .task(id:) прима додатни параметар — идентификатор. Када се вредност идентификатора промени, SwiftUI отказује тренутни задатак и покреће нови са новим идентификатором. Ово је идеално за екране где подаци зависе од изабраног параметра — на пример, листа чланака по категорији или детаљи производа по 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 {
// обради грешку
}
}
}
Када се categoryId промени, SwiftUI отказује претходни захтев и покреће нови. Ово је посебно важно при брзом пребацивању категорија — стари захтеви се неће такмичити са новим за ажурирање стања. Без .task(id:) морали би ручно да пратите промене кроз .onChange и ручно управљате Task-ом.
Иако .task аутоматски отказује задатак при нестанку View, сама асинхрона операција мора кооперативно да проверава отказивање. Swift користи кооперативни модел отказивања — Task.cancel() не зауставља извршење присилно, већ само поставља флаг isCancelled. Код унутар задатка треба периодично да проверава овај флаг.
struct LoadingView: View {
@State var progress: Double = 0
var body: some View {
ProgressView(value: progress)
.task {
for i in 0..<100 {
// Провери отказивање
try Task.checkCancellation()
await Task.sleep(nanoseconds: 50_000_000)
progress = Double(i + 1) / 100.0
}
}
}
}
Task.checkCancellation() баца CancellationError ако је задатак отказан. Ово је најједноставнији начин провере — ради у било ком async контексту. Алтернатива — ручна провера Task.isCancelled пре скупих операција. За URLSession мрежни захтеви се аутоматски отказују при отказивању задатка, јер URLSession подржава Structured Concurrency из корена.
.task је погодан за мноштво сценарија: од једноставног учитавања JSON до сложених паралелних операција са TaskGroup. Размотримо три типична примера употребе.
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("Није успело учитавање")
}
}
.task {
defer { isLoading = false }
do {
profile = await APIClient().fetchProfile()
} catch {
// профил остаје nil
}
}
}
}
struct DashboardView: View {
@State var stats: DashboardStats?
var body: some View {
Text("Командна табла")
.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
}
}
}
Најчешћа грешка — мутација UI својстава унутар .task без пребацивања на главну нит. Иако SwiftUI аутоматски враћа ажурирања на главну нит при промени @State у async контексту, директне манипулације UIKit елементима унутар .task могу изазвати crash.
// ❌ Грешка: без обраде грешака
.task {
let data = await fetchData() // пада при грешци!
items = data
}
// ✅ Исправно: do/catch
.task {
do {
items = await fetchData()
} catch {
errorMessage = error.localizedDescription
}
}
Често постављана питања
.task аутоматски отказује задатак при нестанку View и подржава Structured Concurrency. Task { } у .onAppear захтева ручно чување референце на задатак и позивање cancel() у .onDisappear. .task је такође лакши за читање — он изричито указује да је учитавање података део животног циклуса View.
Да, за претплату на AsyncSequence или AsyncStream користи for await value in publisher.values унутар .task. Ово ради како са async/await, тако и са Combine кроз проширење Publisher.values. При нестанку View итерација ће се аутоматски завршити, а претплата ће бити отказана.
Да, ако се View унутар TabView поново креира при сваком пребацивању. Од iOS 18 TabView може да чува View у меморији — у том случају .task се не покреће поново. Користи .task(id:) са идентификатором картице ако треба поново учитавати податке при сваком пребацивању.
SwiftUI отказује задатак при нестанку View. Ако је URLSession захтев био унутар задатка, он ће такође бити отказан. Ако задатак не подржава кооперативно отказивање (на пример, не проверава isCancelled), наставиће извршење, али његов резултат неће бити примењен на стање, јер View више не постоји.
Да, можете додати више .task модификатора на један View. Сваки креира независан задатак. Ово је згодно за раздвајање различитих извора података: један .task за учитавање профила, други за претплату на WebSocket, трећи за праћење геолокације.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође