.task { } — шта је то, модификатор async и учитавање података у View

Аутор: IT Sectr Објављено: 2026-06-26 Време читања: 9 мин

.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 { } — SwiftUI модификатор за асинхроно учитавање података при појављивању View, доступан од iOS 15.
  • Аутоматско отказивање — при нестанку View SwiftUI отказује Task, спречавајући цурење меморије.
  • async/await контекст — унутар .task доступни су async позиви без потребе за DispatchQueue или Combine.
  • .task(id:) — варијанта са идентификатором поново покреће задатак при промени наведене вредности.
  • Structured Concurrency — .task подржава Structured Concurrency и TaskGroup за паралелне операције.

Шта је .task { } у SwiftUI

.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 кода и елиминише ризик заборава отказивања задатка.

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: кључне разлике

Многи програмери су навикли да учитавају податке у .onAppear, али са појавом async/await и .task овај приступ је застарео. .onAppear извршава код синхроно — за асинхроне операције унутар .onAppear потребно је умотати позив у Task { } и ручно чувати референцу на њега за евентуално отказивање. .task то ради аутоматски.

Карактеристика.task { }.onAppear
Async контекстУграђени async/awaitЗахтева Task { } омотач
АутоотказивањеДа, при нестанку ViewНе, потребно ручно имплементирати
Structured ConcurrencyПодржаваНе подржава
Поновно покретањеСамо при промени idСваки пут при појављивању
Препорука AppleПожељан начинЗа синхроне операције

.onAppear је и даље користан за синхроне операције — на пример, логирање или почетно подешавање UI. Али за асинхроно учитавање података, мрежне захтеве, рад са базом података или системом датотека користи .task. Ово је сигурније и чистије са архитектуралне тачке гледишта.

swift
// ❌ Стари приступ: 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:) — поновно покретање при промени података

Модификатор .task(id:) прима додатни параметар — идентификатор. Када се вредност идентификатора промени, SwiftUI отказује тренутни задатак и покреће нови са новим идентификатором. Ово је идеално за екране где подаци зависе од изабраног параметра — на пример, листа чланака по категорији или детаљи производа по 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 {
            // обради грешку
        }
    }
}

Када се categoryId промени, SwiftUI отказује претходни захтев и покреће нови. Ово је посебно важно при брзом пребацивању категорија — стари захтеви се неће такмичити са новим за ажурирање стања. Без .task(id:) морали би ручно да пратите промене кроз .onChange и ручно управљате Task-ом.

Отказивање задатака и провера isCancelled

Иако .task аутоматски отказује задатак при нестанку View, сама асинхрона операција мора кооперативно да проверава отказивање. Swift користи кооперативни модел отказивања — Task.cancel() не зауставља извршење присилно, већ само поставља флаг isCancelled. Код унутар задатка треба периодично да проверава овај флаг.

swift
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

.task је погодан за мноштво сценарија: од једноставног учитавања JSON до сложених паралелних операција са TaskGroup. Размотримо три типична примера употребе.

Учитавање са обрадом грешака

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("Није успело учитавање")
            }
        }
        .task {
            defer { isLoading = false }
            do {
                profile = await APIClient().fetchProfile()
            } catch {
                // профил остаје nil
            }
        }
    }
}

Паралелно учитавање са TaskGroup

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

Типичне грешке са .task

Најчешћа грешка — мутација UI својстава унутар .task без пребацивања на главну нит. Иако SwiftUI аутоматски враћа ажурирања на главну нит при промени @State у async контексту, директне манипулације UIKit елементима унутар .task могу изазвати crash.

  • Заборавио си try/catch — .task не обрађује грешке аутоматски. Све функције које бацају изузетке унутар морају бити умотане у do/catch, иначе ће апликација пасти.
  • Race condition — ако је неколико .task(id:) покренуто са различитим id-овима и ажурирају исто стање, могуће су трке. Користи одвојена својства за различите изворе података.
  • Дуге синхроне операције — .task не чини синхрони код асинхроним. Ако унутар .task постоји тежак синхрони посао, умотај га у Task.detached или премести у посебан async метод.
  • Игнорисање CancellationError — при провери Task.checkCancellation() грешка CancellationError треба да се пропагира навише, а не да се потискује. Потискивање отказивања може довести до цурења меморије.
swift
// ❌ Грешка: без обраде грешака
.task {
    let data = await fetchData() // пада при грешци!
    items = data
}

// ✅ Исправно: do/catch
.task {
    do {
        items = await fetchData()
    } catch {
        errorMessage = error.localizedDescription
    }
}

Често постављана питања

Која је разлика између .task и коришћења Task { } у .onAppear?

.task аутоматски отказује задатак при нестанку View и подржава Structured Concurrency. Task { } у .onAppear захтева ручно чување референце на задатак и позивање cancel() у .onDisappear. .task је такође лакши за читање — он изричито указује да је учитавање података део животног циклуса View.

Може ли се .task користити за претплату на publisher?

Да, за претплату на AsyncSequence или AsyncStream користи for await value in publisher.values унутар .task. Ово ради како са async/await, тако и са Combine кроз проширење Publisher.values. При нестанку View итерација ће се аутоматски завршити, а претплата ће бити отказана.

Како .task ради са TabView — да ли се задатак поново покреће при пребацивању картица?

Да, ако се View унутар TabView поново креира при сваком пребацивању. Од iOS 18 TabView може да чува View у меморији — у том случају .task се не покреће поново. Користи .task(id:) са идентификатором картице ако треба поново учитавати податке при сваком пребацивању.

Шта се дешава ако View са .task нестане пре завршетка захтева?

SwiftUI отказује задатак при нестанку View. Ако је URLSession захтев био унутар задатка, он ће такође бити отказан. Ако задатак не подржава кооперативно отказивање (на пример, не проверава isCancelled), наставиће извршење, али његов резултат неће бити примењен на стање, јер View више не постоји.

Може ли се користити више .task на једном View?

Да, можете додати више .task модификатора на један View. Сваки креира независан задатак. Ово је згодно за раздвајање различитих извора података: један .task за учитавање профила, други за претплату на WebSocket, трећи за праћење геолокације.

Резиме

  • .task { } — SwiftUI модификатор за асинхроне операције са аутоматским отказивањем при нестанку View.
  • Подршка async/await — унутар .task доступан је потпун async контекст без потребе за Task омотачем.
  • .task(id:) — поново покреће задатак при промени идентификатора, замењује ручни .onChange.
  • Кооперативно отказивање — користи Task.checkCancellation() за проверу отказивања унутар задатка.
  • Structured Concurrency — .task подржава TaskGroup и паралелне операције са отказивањем подређених задатака.
  • Замена .onAppear — за асинхроно учитавање података користи .task уместо комбинације .onAppear + Task + .onDisappear.
  • Обрада грешака — сви async позиви унутар .task морају бити умотани у do/catch ради спречавања crash-а.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође