.task { } — apa itu, modifier async dan pemuatan data di View

Penulis: IT Sectr Diterbitkan: 2026-06-26 Waktu membaca: 9 mnt

.task { } — adalah modifier di SwiftUI, diperkenalkan di iOS 15, yang menjalankan operasi asinkron saat View muncul dan secara otomatis membatalkannya saat View menghilang. Berbeda dengan .onAppear yang mengeksekusi kode sinkron tanpa kemampuan pembatalan, .task bekerja dengan konteks async/await dan memperhatikan siklus hidup View: saat View menghilang, SwiftUI memanggil cancel() pada Task yang dibuat. Ini mencegah kebocoran memori dan eksekusi operasi setelah View tidak perlu lagi diperbarui. Menurut Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024), .task adalah cara yang lebih disukai untuk memuat data di SwiftUI, karena bekerja dengan aman bersama Structured Concurrency dan secara otomatis mengelola masa pakai operasi asinkron.

Poin Utama

  • .task { } — modifier SwiftUI untuk pemuatan data asinkron saat View muncul, tersedia sejak iOS 15.
  • Pembatalan otomatis — saat View menghilang, SwiftUI membatalkan Task, mencegah kebocoran memori.
  • Konteks async/await — di dalam .task tersedia panggilan async tanpa perlu DispatchQueue atau Combine.
  • .task(id:) — varian dengan identifier memulai ulang tugas saat nilai yang ditentukan berubah.
  • Structured Concurrency — .task mendukung Structured Concurrency dan TaskGroup untuk operasi paralel.

Apa itu .task { } di SwiftUI

.task { } — adalah modifier View yang membuat Task dalam konteks async saat View muncul di layar. SwiftUI menjalankan closure yang diberikan di latar belakang, sementara thread utama tetap bebas untuk operasi UI. Saat View menghilang, SwiftUI secara otomatis membatalkan Task melalui mekanisme Structured Concurrency — ini menjamin bahwa operasi asinkron tidak akan berlanjut setelah hasilnya tidak lagi dibutuhkan siapa pun.

Menurut Apple — Swift Programming Language (2025), .task menggunakan konsep Structured Concurrency yang diperkenalkan di Swift 5.5. Setiap .task membuat tugas anak dalam kerangka tugas induk View. Jika tugas induk dibatalkan (View menghilang), semua tugas anak juga otomatis dibatalkan. Ini secara radikal menyederhanakan pengelolaan siklus hidup operasi asinkron dibandingkan dengan menyimpan referensi ke DispatchWorkItem atau AnyCancellable secara manual.

Berbeda dengan pendekatan tradisional dengan @State + panggilan manual di .onAppear, .task tidak memerlukan penyimpanan referensi ke Task untuk pembatalan nanti. SwiftUI melakukannya secara otomatis, yang mengurangi jumlah kode boilerplate dan menghilangkan risiko lupa membatalkan tugas.

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: perbedaan utama

Banyak pengembang terbiasa memuat data di .onAppear, tetapi dengan munculnya async/await dan .task, pendekatan ini sudah usang. .onAppear mengeksekusi kode secara sinkron — untuk operasi asinkron di dalam .onAppear, Anda perlu membungkus panggilan dalam Task { } dan secara manual menyimpan referensi ke sana untuk kemungkinan pembatalan. .task melakukannya secara otomatis.

Karakteristik.task { }.onAppear
Konteks asyncasync/await bawaanMemerlukan pembungkus Task { }
Pembatalan otomatisYa, saat View menghilangTidak, perlu diimplementasikan manual
Structured ConcurrencyMendukungTidak mendukung
Mulai ulangHanya saat id berubahSetiap kali muncul
Rekomendasi AppleCara yang lebih disukaiUntuk operasi sinkron

.onAppear masih berguna untuk operasi sinkron — misalnya, logging atau konfigurasi awal UI. Tetapi untuk pemuatan data asinkron, permintaan jaringan, bekerja dengan database atau sistem file, gunakan .task. Ini lebih aman dan lebih bersih dari segi arsitektur.

swift
// ❌ Pendekatan lama: Task di .onAppear tanpa pembatalan
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
    .onAppear {
        loadTask = Task { await loadData() }
    }
    .onDisappear { loadTask?.cancel() }

// ✅ Pendekatan modern: .task mengelola pembatalan
func body() { var body: some View { Text("") }
    .task { await loadData() }

.task(id:) — memulai ulang saat data berubah

Modifier .task(id:) menerima parameter tambahan — sebuah identifier. Saat nilai identifier berubah, SwiftUI membatalkan tugas saat ini dan memulai tugas baru dengan identifier baru. Ini ideal untuk layar di mana data bergantung pada parameter yang dipilih — misalnya, daftar artikel berdasarkan kategori atau detail produk berdasarkan 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 {
            // tangani kesalahan
        }
    }
}

Saat categoryId berubah, SwiftUI membatalkan permintaan sebelumnya dan memulai yang baru. Ini sangat penting saat berpindah kategori dengan cepat — permintaan lama tidak akan bersaing dengan yang baru untuk memperbarui status. Tanpa .task(id:) Anda harus melacak perubahan secara manual melalui .onChange dan mengelola Task secara manual.

Pembatalan tugas dan pemeriksaan isCancelled

Meskipun .task secara otomatis membatalkan tugas saat View menghilang, operasi asinkron itu sendiri harus secara kooperatif memeriksa pembatalan. Swift menggunakan model pembatalan kooperatif — Task.cancel() tidak menghentikan eksekusi secara paksa, tetapi hanya menetapkan bendera isCancelled. Kode di dalam tugas harus secara periodik memeriksa bendera ini.

swift
struct LoadingView: View {
    @State var progress: Double = 0
    
    var body: some View {
        ProgressView(value: progress)
            .task {
                for i in 0..<100 {
                    // Periksa pembatalan
                    try Task.checkCancellation()
                    
                    await Task.sleep(nanoseconds: 50_000_000)
                    progress = Double(i + 1) / 100.0
                }
            }
    }
}

Task.checkCancellation() melempar CancellationError jika tugas telah dibatalkan. Ini adalah cara pemeriksaan paling sederhana — berfungsi di konteks async mana pun. Alternatif — memeriksa Task.isCancelled secara manual sebelum operasi yang mahal. Untuk URLSession, permintaan jaringan otomatis dibatalkan saat tugas dibatalkan, karena URLSession mendukung Structured Concurrency sejak awal.

Contoh praktis .task

.task cocok untuk banyak skenario: dari pemuatan JSON sederhana hingga operasi paralel kompleks dengan TaskGroup. Mari kita lihat tiga contoh penggunaan yang umum.

Pemuatan dengan penanganan kesalahan

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("Gagal memuat")
            }
        }
        .task {
            defer { isLoading = false }
            do {
                profile = await APIClient().fetchProfile()
            } catch {
                // profil tetap nil
            }
        }
    }
}

Pemuatan paralel dengan TaskGroup

swift
struct DashboardView: View {
    @State var stats: DashboardStats?
    
    var body: some View {
        Text("Dasbor")
            .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
            }
    }
}

Kesalahan umum dengan .task

Kesalahan paling umum — memutasi properti UI di dalam .task tanpa beralih ke thread utama. Meskipun SwiftUI secara otomatis mengembalikan pembaruan ke thread utama saat @State berubah dalam konteks async, manipulasi langsung elemen UIKit di dalam .task dapat menyebabkan crash.

  • Lupa try/catch — .task tidak menangani kesalahan secara otomatis. Semua fungsi yang melempar kesalahan di dalamnya harus dibungkus dalam do/catch, jika tidak aplikasi akan crash.
  • Race condition — jika beberapa .task(id:) dijalankan dengan id yang berbeda dan memperbarui status yang sama, kemungkinan terjadi race condition. Gunakan properti terpisah untuk sumber data yang berbeda.
  • Operasi sinkron yang lama — .task tidak membuat kode sinkron menjadi asinkron. Jika di dalam .task ada pekerjaan sinkron yang berat, bungkus dalam Task.detached atau pindahkan ke metode async terpisah.
  • Mengabaikan CancellationError — saat memeriksa Task.checkCancellation(), kesalahan CancellationError harus diteruskan ke atas, bukan ditekan. Menekan pembatalan dapat menyebabkan kebocoran memori.
swift
// ❌ Kesalahan: tanpa penanganan kesalahan
.task {
    let data = await fetchData() // crash saat kesalahan!
    items = data
}

// ✅ Benar: do/catch
.task {
    do {
        items = await fetchData()
    } catch {
        errorMessage = error.localizedDescription
    }
}

Pertanyaan yang Sering Diajukan

Apa perbedaan antara .task dan penggunaan Task { } di .onAppear?

.task secara otomatis membatalkan tugas saat View menghilang dan mendukung Structured Concurrency. Task { } di .onAppear memerlukan penyimpanan referensi manual ke tugas dan pemanggilan cancel() di .onDisappear. .task juga lebih mudah dibaca — secara eksplisit menunjukkan bahwa pemuatan data adalah bagian dari siklus hidup View.

Bisakah .task digunakan untuk berlangganan ke publisher?

Ya, untuk berlangganan ke AsyncSequence atau AsyncStream, gunakan for await value in publisher.values di dalam .task. Ini berfungsi baik dengan async/await maupun dengan Combine melalui ekstensi Publisher.values. Saat View menghilang, iterasi otomatis berakhir dan langganan dibatalkan.

Bagaimana .task bekerja dengan TabView — apakah tugas dimulai ulang saat tab diganti?

Ya, jika View di dalam TabView dibuat ulang setiap kali tab diganti. Mulai iOS 18, TabView dapat menyimpan View di memori — dalam hal ini .task tidak dimulai ulang. Gunakan .task(id:) dengan identifier tab jika Anda perlu memuat ulang data setiap kali tab diganti.

Apa yang terjadi jika View dengan .task menghilang sebelum permintaan selesai?

SwiftUI membatalkan tugas saat View menghilang. Jika permintaan URLSession ada di dalam tugas, permintaan tersebut juga akan dibatalkan. Jika tugas tidak mendukung pembatalan kooperatif (misalnya, tidak memeriksa isCancelled), tugas akan terus berjalan, tetapi hasilnya tidak akan diterapkan ke status karena View sudah tidak ada.

Bisakah beberapa .task digunakan pada satu View?

Ya, Anda dapat menambahkan beberapa modifier .task pada satu View. Masing-masing membuat tugas independen. Ini berguna untuk memisahkan sumber data yang berbeda: satu .task untuk memuat profil, kedua untuk berlangganan ke WebSocket, ketiga untuk memantau geolokasi.

Ringkasan

  • .task { } — modifier SwiftUI untuk operasi asinkron dengan pembatalan otomatis saat View menghilang.
  • Dukungan async/await — di dalam .task tersedia konteks async lengkap tanpa perlu pembungkus Task.
  • .task(id:) — memulai ulang tugas saat identifier berubah, menggantikan .onChange manual.
  • Pembatalan kooperatif — gunakan Task.checkCancellation() untuk memeriksa pembatalan di dalam tugas.
  • Structured Concurrency — .task mendukung TaskGroup dan operasi paralel dengan pembatalan tugas anak.
  • Pengganti .onAppear — untuk pemuatan data asinkron, gunakan .task sebagai pengganti kombinasi .onAppear + Task + .onDisappear.
  • Penanganan kesalahan — semua panggilan async di dalam .task harus dibungkus dalam do/catch untuk mencegah crash.

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga