Belajar Swift - Concurrency & Async Programming
Episode 9 of 23

Belajar Swift - Concurrency & Async Programming

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

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

DispatchQueue

Grand Central Dispatch (GCD) adalah fondasi concurrency di platform Apple. Kerja utamanya diorganisasi lewat DispatchQueue — antrean tempat blok kode dieksekusi:

DispatchQueue async
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.

Serial vs Concurrent Queue

GCD membedakan dua jenis antrean:

  • Serial queue: satu tugas selesai sebelum tugas berikutnya, aman tanpa lock tambahan.
  • Concurrent queue: beberapa tugas berjalan bersamaan, butuh sinkronisasi data yang dibagikan.
Antrean serial
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.

Structured Concurrency dengan async/await

Berjalan Paralel dengan async let

Episode 8 memperkenalkan async/await untuk kode sekuensial. Untuk menjalankan beberapa operasi paralel, Swift menyediakan async let:

async let paralel
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.

Task Group untuk Tugas Dinamis

Saat jumlah tugas tidak diketahui saat kompilasi — misalnya mengunduh banyak file — gunakan TaskGroup:

TaskGroup dinamis
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.

Actors dan Data Isolation

Actor Melindungi State

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 dengan counter
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 dan Mencegah Race Condition

Task mewakili unit kerja async. Ia bisa dibuat di mana saja dan berjalan di executor yang dikelola Swift:

Membuat Task
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.

Sendable dan Kepatuhan

Tipe yang boleh berpindah antar task harus conform ke protocol Sendable:

Struktur 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.

Penutup

Inti yang harus dibawa pulang:

  • GCD memakai DispatchQueue; mutasi UI hanya boleh di main thread.
  • async let menjalankan beberapa operasi paralel dan menunggu semuanya.
  • TaskGroup mengelola fan-out tugas yang jumlahnya dinamis.
  • Actor mengisolasi mutable state dan compiler menjamin sinkronisasinya.
  • Task adalah unit kerja async yang dieksekusi pada executor Swift.
  • Swift 6 menegakkan data isolation: Sendable dan actor mencegah race condition sejak kompilasi.

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!