Section 7/132 menit
7. Isolation + Async/Await
7. Isolation + Async/Await
7.1 Teori: Executor dan Context Switching
Swift's async/await tidak menggunakan thread-per-task seperti Grand Central Dispatch. Sebaliknya, ia menggunakan cooperative multitasking dengan executor:
- Setiap isolation domain (actor) punya executor (mirip serial queue, tapi lebih efisien)
- Sebuah Task berjalan pada executor tertentu pada satu waktu
- Ketika sebuah Task melakukan
await, ia melepaskan executor-nya (bukan memblokir thread) - Thread yang dibebaskan bisa langsung mengerjakan Task lain
- Ketika
awaitselesai, Task dijadwalkan ulang ke executor yang sesuai
swift
Thread Pool (misalnya 4 core):
┌────────┬────────┬────────┬────────┐
│Thread 1│Thread 2│Thread 3│Thread 4│
└───┬────┴───┬────┴───┬────┴────────┘
│ │ │
│ Task A │ Task B │ Task C
│ (@Main)│ (actor)│ (nonisolated)
│ │ │
▼await │ │
[suspend]│ │ ← Thread 1 bebas!
│ │
Thread 1 mengerjakan Task D sementara Task A menunggu
Ini jauh lebih efisien dari "satu thread per request" karena satu thread bisa mengerjakan ratusan Tasks yang saling bergantian.
7.2 Isolation Propagation
Ketika sebuah async function dipanggil, ia mewarisi isolation dari caller-nya — kecuali function itu sendiri punya anotasi isolation berbeda:
swift
@MainActor
func updateUI(with text: String) async {
label.text = text // di main thread (diwarisi dari @MainActor)
let processed = await processText(text) // hop ke nonisolated context
label.text = processed // kembali ke main thread (kembali ke @MainActor)
}
// Fungsi ini TIDAK diisolasi — berjalan di background cooperative pool
func processText(_ text: String) async -> String {
// heavy processing di background
return text.uppercased()
}
Aturan propagasi:
- Jika caller
@MainActordan callee tidak punya anotasi → callee berjalan di background, callerawaitdan kembali ke main - Jika caller adalah
actordan callee adalahactorlain → hop terjadi, butuhawait - Jika caller dan callee sama isolation domain → tidak ada hop, tidak ada overhead
7.3 Task dan Isolation
Task mewarisi isolation dari konteks pembuatannya:
swift
@MainActor
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Task ini MEWARISI @MainActor isolation dari ViewController
Task {
// Berada di @MainActor — aman untuk UI update
let data = await fetchData() // hop ke background sementara
updateUI(with: data) // kembali ke @MainActor — aman
}
// Task.detached TIDAK mewarisi isolation — berjalan di pool umum
Task.detached {
// BUKAN @MainActor — tidak aman untuk UI update langsung
let data = await self.fetchData()
await MainActor.run {
self.updateUI(with: data) // harus eksplisit
}
}
}
}
Gunakan Task.detached hanya ketika:
- Kamu secara eksplisit tidak ingin mewarisi isolation parent
- Task benar-benar independent dan tidak boleh terpengaruh cancellation dari parent
- Kamu ingin prioritas eksekusi berbeda dari parent
Default: gunakan Task {} biasa — lebih aman, kode lebih bersih.
7.4 Melintasi Isolation Boundary
Setiap crossing isolation boundary memerlukan await:
swift
actor DataStore {
var items: [String] = []
func addItem(_ item: String) {
items.append(item)
}
}
@MainActor
func addAndDisplay(item: String, to store: DataStore) async {
// Crossing: @MainActor → DataStore actor
await store.addItem(item) // hop diperlukan
let count = await store.items.count // hop lagi
label.text = "Total: \(count)" // kembali ke @MainActor
}
Visualisasi boundary crossing:
swift
@MainActor DataStore actor
───────────────────── ──────────────────
addAndDisplay() starts
─ await store.addItem() ─▶ addItem() runs
(suspended) items.append()
◀─────────────────────── done
resumes
label.text = ...
7.5 withIsolation dan assumeIsolated
swift
// assumeIsolated: "saya yakin kita sudah di isolation domain ini"
// Berguna ketika kamu memanggil dari callback yang compiler tidak bisa trace
actor MyActor {
var value = 0
func doSomething() {
// Jalankan closure secara synchronous dalam isolation domain ini
self.assumeIsolated { isolatedSelf in
isolatedSelf.value += 1 // OK karena kita sudah dalam domain
}
}
// Contoh nyata: delegate callback dari library lama
func setupCallback() {
SomeLegacyLibrary.onEvent = { [weak self] in
// Library ini memanggil di thread yang tidak diketahui
// Kita yakin kita harus handle ini dalam actor
self?.assumeIsolated { actor in
actor.handleEvent()
}
}
}
private func handleEvent() {
value += 1
}
}
Kapan assumeIsolated:
- Bridging dari callback-based API ke actor
- Ketika kamu yakin (melalui dokumentasi atau code audit) bahwa kamu sudah dalam isolation domain yang benar
- JANGAN gunakan untuk menghindari compiler error jika kamu tidak yakin