.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 { } — 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.
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
}
}
}
}
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 async | async/await bawaan | Memerlukan pembungkus Task { } |
| Pembatalan otomatis | Ya, saat View menghilang | Tidak, perlu diimplementasikan manual |
| Structured Concurrency | Mendukung | Tidak mendukung |
| Mulai ulang | Hanya saat id berubah | Setiap kali muncul |
| Rekomendasi Apple | Cara yang lebih disukai | Untuk 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.
// ❌ 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() }
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.
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.
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.
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.
.task cocok untuk banyak skenario: dari pemuatan JSON sederhana hingga operasi paralel kompleks dengan TaskGroup. Mari kita lihat tiga contoh penggunaan yang umum.
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
}
}
}
}
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 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.
// ❌ 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
.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.
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.
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.
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.
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
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.
Baca juga