Episode ini membahas concurrency di Swift: Grand Central Dispatch dengan DispatchQueue, structured concurrency dengan async/await, actors dan tasks dengan task groups, serta cara mencegah race condition dan menjamin thread safety lewat data isolation di Swift 6.

Aplikasi yang responsif menangani banyak pekerjaan secara bersamaan: mengambil data, memproses input, dan memperbarui UI tanpa saling memblokir. Episode 9 membahas concurrency dan async programming di Swift — dari Grand Central Dispatch yang sudah berusia puluhan tahun sampai structured concurrency modern dengan async/await, actors, dan task groups.
Concurrency adalah topik di mana kesalahan tidak selalu muncul saat diuji, tetapi meledak di produksi. Memahami model eksekusi dan cara mengisolasi data akan menyelamatkan kalian dari bug yang paling sulit dilacak: race condition.
Grand Central Dispatch (GCD) adalah fondasi concurrency di platform Apple. Kerja utamanya diorganisasi lewat DispatchQueue — antrean tempat blok kode dieksekusi:
import Dispatch
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async {
let hasil = (1...1_000_000).reduce(0, +)
DispatchQueue.main.async {
print("Hasil: \(hasil)")
}
}queue.async { ... } menjadwalkan blok di antrean global, dan DispatchQueue.main.async memindahkan hasil kembali ke main thread untuk memperbarui UI. Main thread wajib menjadi satu-satunya tempat mutasi UI — mengubah UI dari thread lain adalah bug klasik.
GCD membedakan dua jenis antrean:
let serial = DispatchQueue(label: "com.example.worker")
serial.sync {
print("Tugas 1")
}
serial.sync {
print("Tugas 2")
}let serial = DispatchQueue(label: "com.example.worker") membuat antrean serial yang menjalankan tugas berurutan. GCD masih dipakai luas untuk pekerjaan berbasis callback, tetapi Swift modern mengarahkan kalian ke structured concurrency yang lebih aman.
Episode 8 memperkenalkan async/await untuk kode sekuensial. Untuk menjalankan beberapa operasi paralel, Swift menyediakan async let:
func ambilData() async throws {
async let pengguna = ambilPengguna(login: "arman")
async let repos = ambilRepositori(login: "arman")
let (u, r) = try await (pengguna, repos)
print(u.name, r.count)
}async let pengguna = ambilPengguna(login: "arman") memulai operasi tanpa menunggu, dan try await (pengguna, repos) menunggu keduanya selesai. Dua request berjalan bersamaan — total waktu mendekati request terlama, bukan penjumlahan keduanya.
Saat jumlah tugas tidak diketahui saat kompilasi — misalnya mengunduh banyak file — gunakan TaskGroup:
func unduhSemua(urls: [URL]) async -> [Data] {
await withTaskGroup(of: Data.self) { group in
for url in urls {
group.addTask {
let (data, _) = try! await URLSession.shared.data(from: url)
return data
}
}
var hasil = [Data]()
for await data in group {
hasil.append(data)
}
return hasil
}
}withTaskGroup(of: Data.self) membuka scope tempat setiap addTask menjalankan work item, dan for await data in group mengumpulkan hasil begitu tersedia. Task group adalah cara paling aman untuk fan-out pekerjaan dengan jumlah dinamis.
Actor adalah tipe yang mengisolasi mutable state: hanya satu task yang boleh mengakses stored properties-nya pada satu waktu. Compiler menegakkan ini — akses dari luar actor harus await:
actor Penghitung {
private var nilai = 0
func increment() -> Int {
nilai += 1
return nilai
}
}
let counter = Penghitung()
Task {
let hasil = await counter.increment()
print(hasil)
}actor Penghitung menjamin tidak ada race condition saat dua task memanggil increment bersamaan — kompilator menyisipkan sinkronisasi secara otomatis. Panggilan dari luar, await counter.increment(), menunggu giliran akses.
Task mewakili unit kerja async. Ia bisa dibuat di mana saja dan berjalan di executor yang dikelola Swift:
Task {
do {
let pengguna = try await ambilPengguna(login: "arman")
print(pengguna.name)
} catch {
print("Gagal: \(error)")
}
}Task { ... } memulai unit kerja asynchronous yang independen. Di Swift 6, data isolation ditegakkan compiler: data yang dibagikan antar task harus aman secara tegas — lewat actor, Sendable, atau nilai yang immutable. Kesalahan race condition yang dulu hanya ketahuan saat runtime sekarang ditolak sejak kompilasi.
Tipe yang boleh berpindah antar task harus conform ke protocol Sendable:
struct Pesan: Sendable {
let id: Int
let isi: String
}struct Pesan: Sendable menyatakan bahwa nilai Pesan aman dikirim melintasi batas task karena seluruh propertinya immutable. Di Swift 6, compiler memeriksa ini — menutup variable mutable dari luar di dalam Task akan menjadi error kompilasi.
Info
Di Swift 5 mode concurrency bersifat warning, sedangkan Swift 6 menegakkannya sebagai error. Aktifkan mode Swift 6 secara bertahap untuk project kalian dan perbaiki setiap peringatan sebelum menyebut codebase kalian thread-safe.
Inti yang harus dibawa pulang:
async let menjalankan beberapa operasi paralel dan menunggu semuanya.Di episode 10 selanjutnya kita akan membahas package management dan modularization — fondasi Swift Package Manager, membuat package dengan dependency dan library, struktur multi-target dengan test target, serta integrasi SPM ke dalam Xcode project. Kode kalian akan menjadi modular dan dapat digunakan kembali!