Pendahuluan
Ketika Swift 6 pertama kali diperkenalkan, para developer disambut dengan janji besar: keamanan data race pada waktu kompilasi. Nggak ada lagi crash misterius di production karena dua thread mengakses data yang sama secara bersamaan. Kedengarannya luar biasa, kan?
Masalahnya, adopsi fitur ini terasa seperti mendaki gunung pakai sandal jepit.
Banyak developer — termasuk yang sudah berpengalaman sekalipun — merasa frustrasi dengan banjirnya error compiler terkait Sendable, anotasi @MainActor yang harus ditebar di mana-mana, dan perilaku nonisolated async yang bikin bingung. Hasilnya? Banyak proyek yang memilih untuk tetap di Swift 5 mode atau menonaktifkan strict concurrency checking sama sekali. Jujur, saya sendiri sempat tergoda melakukan hal yang sama di beberapa proyek.
Nah, Apple ternyata mendengarkan keluhan ini. Di Swift 6.2, yang hadir bersama Xcode 26, mereka memperkenalkan serangkaian fitur yang disebut Approachable Concurrency — sebuah pendekatan baru yang membuat concurrency di Swift jauh lebih mudah didekati tanpa mengorbankan keamanan. Filosofinya sederhana: kode Anda seharusnya dimulai dengan sederhana, dan kompleksitas concurrency hanya ditambahkan ketika benar-benar dibutuhkan.
Dalam artikel ini, kita akan membahas secara mendalam semua fitur utama Approachable Concurrency di Swift 6.2:
- Default Actor Isolation dengan MainActor secara default
- nonisolated(nonsending) dan perilaku baru fungsi async
- Atribut @concurrent untuk pekerjaan berat di background
- Infer Isolated Conformances (SE-0470) untuk menghilangkan error conformance yang mengganggu
- Panduan migrasi langkah demi langkah dari Swift 6.0 ke Swift 6.2
- Studi kasus nyata dan best practices
Oke, langsung aja kita mulai.
Apa Itu Approachable Concurrency?
Sebelum masuk ke detail teknis, penting untuk memahami filosofi di balik Approachable Concurrency. Tim Swift mengadopsi prinsip yang mereka sebut progressive disclosure — sebuah konsep desain di mana fitur-fitur lanjutan disembunyikan sampai pengguna benar-benar membutuhkannya.
Bayangkan seperti ini: ketika Anda belajar mengemudi, Anda nggak langsung diminta memahami cara kerja transmisi manual, turbocharger, dan sistem rem ABS secara bersamaan. Anda mulai dengan dasar-dasarnya dulu, lalu secara bertahap mempelajari fitur yang lebih canggih. Nah, Approachable Concurrency menerapkan filosofi yang sama untuk kode Swift.
Tiga Fase Progressive Disclosure
Swift 6.2 membagi pengalaman concurrency menjadi tiga fase yang jelas:
- Kode Sequential (Tahap 1) — Tulis kode seperti biasa, tanpa anotasi apa pun. Semuanya berjalan di main thread secara default. Nggak ada data race karena nggak ada concurrency. Ini cocok untuk sebagian besar kode aplikasi: mengupdate UI, mengelola state, dan logika bisnis sederhana.
- Async/Await (Tahap 2) — Ketika Anda perlu melakukan operasi yang membutuhkan waktu (seperti network request atau membaca file), gunakan
async/await. Ini memberikan suspension tanpa paralelisme. Kode Anda bisa menunggu hasil tanpa memblokir thread, tapi tidak ada pekerjaan yang berjalan secara paralel. - Paralelisme Eksplisit (Tahap 3) — Hanya ketika Anda benar-benar butuh performa ekstra (misalnya memproses gambar berukuran besar atau parsing JSON yang berat), Anda secara eksplisit memindahkan pekerjaan ke background thread pakai atribut
@concurrent.
Yang menarik dari pendekatan ini: kebanyakan developer hanya akan berurusan dengan Tahap 1 dan 2 untuk sebagian besar pekerjaan sehari-hari mereka. Tahap 3 hanya diperlukan untuk skenario performa khusus — dan honestly, itu cukup jarang.
Mengaktifkan di Proyek Anda
Untuk proyek baru yang dibuat di Xcode 26, Approachable Concurrency sudah aktif secara default. Nggak perlu ngapa-ngapain — cukup buat proyek baru dan mulai coding. Untuk proyek yang sudah ada, Anda perlu mengaktifkannya secara manual melalui build settings, yang akan kita bahas di bagian selanjutnya.
Default Actor Isolation: MainActor Secara Default
Ini adalah perubahan terbesar dan paling berdampak di Approachable Concurrency. Serius, ini game changer.
Dengan mengatur defaultIsolation ke MainActor.self, semua tipe di modul Anda secara implisit mendapatkan isolasi @MainActor. Artinya? Anda nggak perlu lagi menuliskan @MainActor di depan setiap class, struct, atau fungsi yang berinteraksi dengan UI. Compiler akan mengasumsikan bahwa semuanya berjalan di main thread kecuali Anda secara eksplisit mengatakan sebaliknya.
Mengatur di Xcode
Untuk proyek Xcode, buka Build Settings pada target Anda, cari "Default Actor Isolation", dan ubah nilainya menjadi MainActor. Sesederhana itu.
Mengatur di Swift Package Manager
Untuk paket SPM, tambahkan pengaturan di Package.swift:
// swift-tools-version: 6.2
.target(
name: "MyFeature",
swiftSettings: [
.defaultIsolation(MainActor.self)
]
)
Contoh Praktis
Mari kita lihat perbedaannya secara konkret. Berikut cara kita menulis ViewModel di Swift 6.0:
// Swift 6.0 - Harus menambahkan @MainActor secara manual
@MainActor
class UserViewModel {
var username = ""
var isLoading = false
func loadProfile() async {
isLoading = true
let profile = try? await APIService.fetchProfile()
username = profile?.name ?? "Unknown"
isLoading = false
}
}
Dan berikut kode yang sama di Swift 6.2 dengan defaultIsolation diatur ke MainActor:
// Swift 6.2 dengan defaultIsolation = MainActor.self
// Kelas ini otomatis terisolasi ke MainActor — tanpa anotasi!
class UserViewModel {
var username = ""
var isLoading = false
func loadProfile() async {
isLoading = true
let profile = try? await APIService.fetchProfile()
username = profile?.name ?? "Unknown"
isLoading = false
}
}
Perhatikan bahwa kode di Swift 6.2 identik dengan kode yang mungkin Anda tulis sebelum era concurrency Swift — tanpa anotasi khusus apa pun. Tapi di balik layar, compiler memastikan bahwa semua akses ke properti username dan isLoading aman dari data race karena semuanya dijamin berjalan di main thread.
Ini kemenangan besar untuk keterbacaan kode. Proyek yang sebelumnya dipenuhi @MainActor di setiap baris sekarang bisa tampil bersih dan rapi, sementara keamanan concurrency tetap terjaga sepenuhnya.
Perlu dicatat bahwa actor yang Anda definisikan sendiri tidak terpengaruh oleh pengaturan defaultIsolation. Kalau Anda punya actor DatabaseManager { ... }, ia tetap punya isolasinya sendiri dan nggak akan dipaksa jadi @MainActor. Pengaturan ini hanya berlaku untuk tipe-tipe yang sebelumnya tidak memiliki isolasi eksplisit.
Memahami nonisolated(nonsending)
Salah satu sumber kebingungan terbesar di Swift concurrency sebelum versi 6.2 adalah perilaku yang nggak konsisten antara fungsi synchronous dan asynchronous yang bersifat nonisolated.
Begini masalahnya: di Swift 6.0, kalau Anda punya fungsi synchronous yang nonisolated, fungsi tersebut akan mewarisi isolasi dari pemanggilnya. Jadi kalau dipanggil dari konteks @MainActor, fungsi itu berjalan di main thread. Masuk akal, kan?
Tapi kalau fungsi tersebut async dan nonisolated, tiba-tiba ia melompat ke background thread secara otomatis! Perilaku ini sangat nggak intuitif dan jadi sumber banyak bug yang sulit dilacak. Developer mengira fungsi async mereka masih di main thread, padahal ternyata sudah berpindah ke background tanpa peringatan yang jelas. Saya pernah menghabiskan hampir setengah hari debugging masalah yang ternyata disebabkan oleh ini.
Solusi Swift 6.2: nonisolated(nonsending)
Swift 6.2 memperkenalkan konsep nonisolated(nonsending) yang memperbaiki inkonsistensi ini. Dengan fitur ini diaktifkan, fungsi nonisolated async sekarang berperilaku konsisten dengan versi synchronous-nya: ia mewarisi isolasi dari pemanggilnya.
Nama "nonsending" berasal dari fakta bahwa fungsi ini tidak "mengirim" eksekusi ke executor lain — ia tetap di mana pun pemanggilnya berada.
// Swift 6.2 dengan NonisolatedNonsendingByDefault
class DataManager {
// Fungsi ini berjalan di actor pemanggil
// Jika dipanggil dari MainActor, ia tetap di MainActor
// Jika dipanggil dari actor lain, ia tetap di actor tersebut
nonisolated func fetchData() async throws -> [String] {
// Karena nonsending by default, fungsi ini TIDAK pindah
// ke background thread — ia tetap di actor pemanggil
let url = URL(string: "https://api.example.com/data")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([String].self, from: data)
}
}
Dengan NonisolatedNonsendingByDefault diaktifkan sebagai upcoming feature, perilaku ini jadi default untuk semua fungsi nonisolated async. Nggak perlu menuliskan nonisolated(nonsending) secara eksplisit — cukup tulis fungsi Anda seperti biasa.
Ini menghilangkan satu kategori besar kebingungan. Sekarang aturannya sederhana dan konsisten: fungsi nonisolated, baik sync maupun async, selalu mewarisi isolasi dari pemanggilnya. Titik. Nggak ada lagi kejutan perpindahan thread yang nggak diharapkan.
Lalu bagaimana kalau Anda memang ingin fungsi berjalan di background thread? Di situlah atribut @concurrent berperan.
Atribut @concurrent: Kapan dan Bagaimana Menggunakannya
Dengan defaultIsolation MainActor dan nonisolated(nonsending) sebagai default, hampir semua kode Anda akan berjalan di main thread. Ini bagus untuk keamanan dan kesederhanaan, tapi ada saat-saat ketika Anda memang perlu menjalankan kode di background thread — terutama untuk operasi yang berat secara komputasi.
Atribut @concurrent adalah cara eksplisit untuk bilang ke compiler: "Fungsi ini boleh berjalan di thread mana saja, termasuk background thread." Ini kebalikan dari perilaku default di Swift 6.2.
Kapan Menggunakan @concurrent
Gunakan @concurrent untuk pekerjaan yang memenuhi kriteria berikut:
- Komputasi berat CPU — seperti pengolahan gambar, kompresi data, atau kalkulasi matematika yang kompleks
- Parsing data berukuran besar — misalnya men-decode JSON dengan ribuan entri
- Transformasi data — sorting, filtering, atau mapping koleksi besar
- Operasi kriptografi — hashing, enkripsi, atau dekripsi
Jangan gunakan @concurrent untuk:
- Network requests sederhana (sudah ditangani oleh
await) - Operasi database ringan
- Update UI apa pun
- Logika bisnis yang berjalan cepat
Contoh Praktis
class ImageProcessor {
// Berjalan di MainActor (default) — aman untuk update UI
func updateUI(with image: UIImage) {
imageView.image = image
}
// Secara eksplisit berjalan di background thread
@concurrent
func processImage(_ data: Data) async -> UIImage? {
// Operasi berat — cocok untuk background thread
guard let image = UIImage(data: data) else { return nil }
let renderer = UIGraphicsImageRenderer(
size: CGSize(width: 300, height: 300)
)
return renderer.image { context in
image.draw(in: CGRect(
origin: .zero,
size: CGSize(width: 300, height: 300)
))
}
}
func loadAndProcessImage(from url: URL) async {
do {
let (data, _) = try await URLSession.shared.data(from: url)
// processImage berjalan di background karena @concurrent
if let processed = await processImage(data) {
// updateUI berjalan di MainActor — aman!
updateUI(with: processed)
}
} catch {
print("Error: \(error)")
}
}
}
Perhatikan alur kode di atas. Fungsi loadAndProcessImage berjalan di MainActor (default). Ketika memanggil processImage yang ditandai @concurrent, Swift otomatis memindahkan eksekusi ke background thread. Setelah selesai, hasilnya dikembalikan dan updateUI berjalan kembali di MainActor.
Semua perpindahan thread ini terjadi secara otomatis dan aman — compiler memastikan nggak ada data race. Cukup elegan, menurut saya.
Tabel Keputusan: Kapan Menggunakan Apa
Berikut panduan cepat untuk memilih isolasi yang tepat:
- Default (tanpa anotasi) — Untuk sebagian besar kode aplikasi. Update UI, logika bisnis, state management. Berjalan di MainActor secara default.
- nonisolated — Untuk fungsi utility yang nggak terikat ke actor tertentu tapi tetap ingin mewarisi isolasi pemanggil. Contoh: helper functions, extensions pada tipe standar.
- @concurrent — Hanya untuk pekerjaan berat yang benar-benar perlu berjalan di background. Gunakan dengan hemat.
Sebagai aturan praktis: kalau ragu, jangan gunakan @concurrent. Biarkan kode berjalan di MainActor. Pindahkan ke background hanya kalau Anda mengamati masalah performa yang nyata melalui profiling dengan Instruments.
Infer Isolated Conformances (SE-0470)
Pernah nggak Anda mengalami error compiler yang bikin frustrasi saat mencoba membuat tipe @MainActor yang conform ke protocol seperti Codable atau Hashable? Kalau pernah, Anda nggak sendirian. Ini salah satu pain point paling umum di Swift 6.0.
Masalahnya
Di Swift 6.0, ketika Anda punya tipe yang diisolasi ke @MainActor dan ingin membuat conformance ke protocol yang tidak terisolasi (seperti Codable), compiler akan menolak dengan error yang cukup membingungkan. Alasannya teknis: method-method yang di-synthesize compiler untuk conformance tersebut nggak punya isolasi yang cocok dengan tipe Anda.
// Sebelum SE-0470: Error!
@MainActor
class SettingsManager: Codable {
var theme: String = "light"
var fontSize: Int = 14
// Compiler error: protocol conformance bukan di MainActor
// Anda harus menulis init(from:) dan encode(to:) secara manual
// atau menggunakan workaround yang tidak elegan
}
Solusi di Swift 6.0 biasanya melibatkan menulis implementasi protocol secara manual dengan anotasi nonisolated, yang sangat merepotkan — terutama untuk protocol seperti Codable yang punya banyak boilerplate.
Solusi SE-0470
Swift 6.2 memperkenalkan Infer Isolated Conformances melalui proposal SE-0470. Dengan fitur ini, compiler secara otomatis mengisolasi conformance protocol untuk mencocokkan isolasi tipe yang mengimplementasikannya.
// Setelah SE-0470 dengan InferIsolatedConformances:
// Conformance ke Codable otomatis diisolasi ke MainActor
@MainActor
class SettingsManager: Codable {
var theme: String = "light"
var fontSize: Int = 14
// Compiler secara otomatis mengisolasi conformance ke MainActor
// Tidak perlu menulis init(from:) dan encode(to:) secara manual!
}
Ini berarti method init(from decoder:) dan encode(to encoder:) yang di-synthesize compiler otomatis dianggap sebagai @MainActor, sesuai dengan isolasi tipe SettingsManager. Nggak ada conflict, nggak ada error — semuanya "just works".
Fitur ini sangat bermanfaat untuk:
- Tipe
@MainActoryang conform keCodable,Decodable, atauEncodable - Tipe
@MainActoryang conform keHashable,Equatable, atauComparable - Tipe yang conform ke protocol custom apa pun yang nggak punya isolasi eksplisit
Untuk mengaktifkannya, tambahkan upcoming feature flag:
.enableUpcomingFeature("InferIsolatedConformances")
Satu hal yang perlu diingat: conformance yang diisolasi ini punya implikasi. Method protocol hanya bisa dipanggil dari konteks yang memiliki isolasi yang sama. Misalnya, method encode(to:) dari SettingsManager di atas hanya bisa dipanggil dari konteks @MainActor. Tapi dalam praktiknya, ini jarang jadi masalah karena Anda biasanya mengakses tipe tersebut dari konteks MainActor juga.
Mengaktifkan Approachable Concurrency: Panduan Langkah demi Langkah
Oke, sekarang kita sudah paham semua fitur-fiturnya. Saatnya kita lihat cara mengaktifkan semuanya sekaligus di proyek Anda.
Untuk Proyek Xcode
Kalau Anda menggunakan proyek Xcode standar, ikuti langkah-langkah berikut:
- Buka proyek Anda di Xcode 26
- Pilih target yang ingin Anda konfigurasi
- Buka tab Build Settings
- Cari "Default Actor Isolation" dan ubah ke MainActor
- Cari "Upcoming Features" atau konfigurasi melalui Other Swift Flags
- Tambahkan flag:
-enable-upcoming-feature NonisolatedNonsendingByDefault - Tambahkan flag:
-enable-upcoming-feature InferIsolatedConformances - Build proyek dan perbaiki warning atau error yang muncul
Untuk Paket Swift Package Manager
Berikut contoh lengkap Package.swift dengan semua fitur Approachable Concurrency diaktifkan:
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "MyAwesomeApp",
platforms: [.iOS(.v18), .macOS(.v15)],
targets: [
.target(
name: "MyFeature",
swiftSettings: [
.defaultIsolation(MainActor.self),
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.enableUpcomingFeature("InferIsolatedConformances")
]
)
]
)
Mari kita bahas apa yang dilakukan setiap pengaturan:
.defaultIsolation(MainActor.self)— Membuat semua tipe dalam target ini secara implisit terisolasi ke MainActor. Nggak perlu lagi nulis@MainActormanual di setiap tipe..enableUpcomingFeature("NonisolatedNonsendingByDefault")— Mengubah perilaku default fungsinonisolated asyncagar mewarisi isolasi pemanggil, bukan melompat ke background thread. Ini menghilangkan banyak kejutan dan bug halus..enableUpcomingFeature("InferIsolatedConformances")— Memungkinkan compiler otomatis mengisolasi conformance protocol agar sesuai dengan isolasi tipe. Bye bye error conformance yang mengganggu.
Ketiga pengaturan ini bekerja bersama secara sinergis. Anda bisa mengaktifkan satu per satu secara bertahap, tapi pengalaman terbaik didapatkan ketika semuanya aktif bersamaan.
Studi Kasus: Migrasi Aplikasi dari Swift 6.0 ke Swift 6.2
Teori memang penting, tapi nggak ada yang mengalahkan contoh nyata. So, mari kita lihat bagaimana sebuah aplikasi todo list sederhana berubah dari Swift 6.0 ke Swift 6.2.
Kode Swift 6.0: Penuh Anotasi Manual
Berikut kode tipikal Swift 6.0 yang mengikuti strict concurrency checking. Perhatikan berapa banyak anotasi yang diperlukan:
// Swift 6.0 - Banyak anotasi manual yang diperlukan
@MainActor
class TodoListViewModel: ObservableObject {
@Published var todos: [TodoItem] = []
@Published var isLoading = false
@Published var errorMessage: String?
private let apiService: APIService
init(apiService: APIService) {
self.apiService = apiService
}
func loadTodos() async {
isLoading = true
defer { isLoading = false }
do {
let fetchedTodos = try await apiService.fetchTodos()
todos = fetchedTodos
} catch {
errorMessage = error.localizedDescription
}
}
// Harus ditandai nonisolated agar bisa berjalan di luar MainActor
nonisolated func parseTodos(_ data: Data) throws -> [TodoItem] {
try JSONDecoder().decode([TodoItem].self, from: data)
}
}
@MainActor
struct TodoListView: View {
@StateObject private var viewModel: TodoListViewModel
var body: some View {
List(viewModel.todos) { todo in
TodoRowView(todo: todo)
}
.overlay {
if viewModel.isLoading {
ProgressView()
}
}
.task {
await viewModel.loadTodos()
}
}
}
Kode di atas sudah cukup bersih karena contoh ini sederhana. Tapi bayangkan proyek dengan puluhan ViewModel, ratusan View, dan berbagai service layer — setiap satu dari mereka butuh @MainActor. Boilerplate-nya bisa bikin pusing.
Kode Swift 6.2: Bersih dan Ringkas
Sekarang lihat kode yang sama setelah dimigrasi ke Swift 6.2 dengan Approachable Concurrency:
// Swift 6.2 dengan Approachable Concurrency
// Tidak perlu @MainActor eksplisit — sudah default!
class TodoListViewModel: ObservableObject {
@Published var todos: [TodoItem] = []
@Published var isLoading = false
@Published var errorMessage: String?
private let apiService: APIService
init(apiService: APIService) {
self.apiService = apiService
}
func loadTodos() async {
isLoading = true
defer { isLoading = false }
do {
let data = try await apiService.fetchRawData()
let fetchedTodos = try await parseTodos(data)
todos = fetchedTodos
} catch {
errorMessage = error.localizedDescription
}
}
// Parsing JSON berat - gunakan @concurrent untuk background thread
@concurrent
func parseTodos(_ data: Data) async throws -> [TodoItem] {
try JSONDecoder().decode([TodoItem].self, from: data)
}
}
// View juga tidak perlu @MainActor eksplisit
struct TodoListView: View {
@StateObject private var viewModel: TodoListViewModel
var body: some View {
List(viewModel.todos) { todo in
TodoRowView(todo: todo)
}
.overlay {
if viewModel.isLoading {
ProgressView()
}
}
.task {
await viewModel.loadTodos()
}
}
}
Apa yang Berubah?
Mari kita bandingkan perubahan kunci antara kedua versi:
- Nggak ada lagi
@MainActoreksplisit — Baik diTodoListViewModelmaupunTodoListView, kita nggak perlu menuliskan@MainActor. Default isolation sudah mengurus ini. nonisolateddiganti@concurrent— Di versi lama,parseTodosditandainonisolatedyang artinya agak ambigu. Di versi baru, kita pakai@concurrentyang secara eksplisit menyatakan: "fungsi ini harus berjalan di background thread." Lebih jelas, nggak ada tebak-tebakan.- Intent yang lebih jelas — Dengan
@concurrent, siapa pun yang baca kode langsung tahu bahwaparseTodosadalah operasi berat yang perlu dijalankan di luar main thread. - Kode lebih natural — Kode Swift 6.2 terlihat seperti kode Swift biasa. Nggak ada "noise" concurrency di mana-mana. Ini menurunkan barrier to entry untuk developer baru di tim Anda.
Pada proyek yang lebih besar dengan ratusan file, penghematan boilerplate ini jadi sangat signifikan. Bukan cuma bikin kode lebih bersih, tapi juga mengurangi kemungkinan bug dari lupa menambahkan anotasi @MainActor.
Tips dan Best Practices
Setelah memahami semua fitur Approachable Concurrency, berikut kumpulan tips yang (menurut pengalaman saya) akan sangat membantu di proyek nyata.
1. Jangan Tandai Semuanya dengan @concurrent
Ini kesalahan paling umum yang mungkin dilakukan developer saat pertama kali pakai fitur baru ini. Logikanya terdengar masuk akal: "Kalau kode berjalan di background, bukankah itu lebih baik untuk performa?"
Jawabannya: nggak, belum tentu.
Perpindahan antar thread (context switching) punya overhead. Kalau Anda menandai fungsi kecil yang selesai dalam mikrodetik dengan @concurrent, overhead perpindahan thread bisa jadi lebih besar dari waktu eksekusi fungsinya sendiri. Prinsipnya sederhana: biarkan kode berjalan di MainActor kecuali ada alasan performa yang terukur untuk memindahkannya.
2. Migrasi Secara Bertahap
Jangan mengaktifkan semua feature flag sekaligus dan berharap semuanya berjalan mulus. Percayalah, itu resep untuk sakit kepala. Sebaiknya aktifkan satu per satu dan perbaiki masalah yang muncul sebelum lanjut ke flag berikutnya. Urutan yang saya sarankan:
- Mulai dengan
InferIsolatedConformances— ini biasanya yang paling aman dan langsung menghilangkan banyak error conformance. - Lanjutkan dengan
defaultIsolation(MainActor.self)— perubahan ini akan bikin banyak anotasi@MainActorjadi redundan. Hapus yang nggak diperlukan secara bertahap. - Terakhir, aktifkan
NonisolatedNonsendingByDefault— ini mengubah perilaku fungsi async yang sudah ada, jadi perlu pengujian paling menyeluruh.
3. Uji Secara Menyeluruh Setelah Migrasi
Meskipun perubahan ini dirancang untuk "source compatible", perubahan perilaku runtime (terutama NonisolatedNonsendingByDefault) bisa mempengaruhi timing dan urutan eksekusi kode Anda. Pastikan untuk:
- Menjalankan seluruh test suite Anda
- Melakukan profiling dengan Instruments untuk memastikan nggak ada regresi performa
- Menguji skenario edge case, terutama yang melibatkan operasi concurrent
- Memperhatikan deadlock potensial — kalau kode yang sebelumnya berjalan di background sekarang berjalan di MainActor, pastikan nggak ada blocking call yang bisa menyebabkan deadlock
4. Pahami Bahwa Actor Tetap Punya Isolasi Sendiri
Pengaturan defaultIsolation nggak mempengaruhi tipe actor yang Anda definisikan. Sebuah actor tetap punya isolasinya sendiri, independen dari setting default. Contohnya:
// Actor ini TIDAK terpengaruh oleh defaultIsolation = MainActor.self
// Ia tetap memiliki isolasi sendiri yang terpisah
actor CacheManager {
private var cache: [String: Data] = [:]
func store(_ data: Data, forKey key: String) {
cache[key] = data
}
func retrieve(forKey key: String) -> Data? {
cache[key]
}
}
// Tipe ini TERPENGARUH — secara implisit @MainActor
class ViewModel {
let cacheManager = CacheManager()
func loadCachedData() async {
// Perlu 'await' karena mengakses actor yang berbeda
let data = await cacheManager.retrieve(forKey: "profile")
// ... gunakan data
}
}
5. Manfaatkan Named Tasks untuk Debugging
Dengan kode yang sekarang lebih banyak berjalan di MainActor, memahami alur eksekusi jadi semakin penting. Pakai named tasks untuk memudahkan debugging:
func refreshAllData() {
Task(name: "RefreshProfile") {
await loadProfile()
}
Task(name: "RefreshSettings") {
await loadSettings()
}
Task(name: "RefreshNotifications") {
await loadNotifications()
}
}
Named tasks muncul di Instruments dan debugger, bikin Anda lebih gampang melacak apa yang terjadi saat sesuatu nggak berjalan sesuai harapan. Kebiasaan kecil, tapi sangat membantu dalam jangka panjang.
6. Waspadai Performa Main Thread
Karena lebih banyak kode berjalan di MainActor secara default, Anda perlu lebih waspada terhadap potensi pemblokiran main thread. Pantau frame rate UI dan gunakan Instruments untuk mengidentifikasi kalau ada pekerjaan berat yang nggak sengaja berjalan di main thread. Kalau ketemu, tandai fungsi tersebut dengan @concurrent.
Kesimpulan
Swift 6.2 dengan Approachable Concurrency adalah langkah besar ke depan dalam membuat concurrency di Swift benar-benar approachable — mudah didekati oleh semua level developer. Yuk kita rangkum apa yang sudah kita pelajari:
- Default Actor Isolation (MainActor) menghilangkan kebutuhan anotasi
@MainActormanual di setiap tipe, bikin kode lebih bersih dan gampang dibaca. - nonisolated(nonsending) memperbaiki inkonsistensi perilaku antara fungsi sync dan async, sehingga fungsi async nggak lagi melompat ke background thread tanpa izin eksplisit.
- Atribut @concurrent memberikan cara yang jelas dan eksplisit untuk memindahkan pekerjaan berat ke background thread, dengan intent yang langsung terbaca dari kodenya.
- Infer Isolated Conformances (SE-0470) menghilangkan error conformance protocol yang selama ini jadi sumber frustrasi besar.
Kombinasi dari semua fitur ini menciptakan model mental yang jauh lebih sederhana: tulis kode secara normal, semuanya aman di main thread, dan hanya pindahkan ke background ketika benar-benar dibutuhkan. Nggak ada lagi tebak-tebakan tentang thread mana yang sedang menjalankan kode Anda.
Progressive disclosure berarti developer pemula bisa langsung produktif tanpa harus memahami seluruh model concurrency Swift, sementara developer berpengalaman tetap punya kontrol penuh. Menurut saya, ini keseimbangan yang tepat antara keamanan dan kemudahan penggunaan.
Kalau Anda belum mencobanya, sekarang waktu yang tepat untuk mulai bereksperimen. Buat branch baru di proyek Anda, aktifkan fitur-fitur Approachable Concurrency satu per satu, dan lihat sendiri berapa banyak boilerplate yang bisa Anda hilangkan — sambil tetap mempertahankan keamanan concurrency kode Anda.
Swift concurrency akhirnya terasa seperti seharusnya sejak awal: kuat, aman, dan gampang dipakai.