Section 3/171 menit

3. Executor: Tulang Punggung Isolation

3. Executor: Tulang Punggung Isolation

Executor adalah abstraksi runtime yang menjadwalkan job di actor. Memahami executor berarti memahami siapa yang menjalankan kode actor dan di mana.

SerialExecutor Protocol

Setiap actor punya executor yang konform ke SerialExecutor:

swift
public protocol SerialExecutor: Executor {
    func enqueue(_ job: consuming ExecutorJob)
    func asUnownedSerialExecutor() -> UnownedSerialExecutor
}

Default-nya, actor pakai default actor executor — implementasi yang dikelola runtime, mirip serial queue lightweight.

Custom Executor: Mengikat Actor ke Thread Tertentu

Kadang kamu butuh actor yang selalu berjalan di queue tertentu — misalnya CoreData context queue, atau DispatchQueue legacy. Override unownedExecutor:

swift
actor DatabaseActor {
    private let queue: DispatchSerialQueue
    private let _executor: any SerialExecutor

    init(queue: DispatchSerialQueue) {
        self.queue = queue
        self._executor = queue.asUnownedSerialExecutor() as! any SerialExecutor
    }

    nonisolated var unownedExecutor: UnownedSerialExecutor {
        queue.asUnownedSerialExecutor()
    }

    func query() { /* dijalankan di `queue` */ }
}

Use case nyata:

  • Bridging dengan API berbasis DispatchQueue yang tidak bisa diubah.
  • Memastikan akses hardware (kamera, sensor, GPU) selalu di queue yang benar.
  • Integrasi dengan CoreData (NSManagedObjectContext queue).

Hati-hati: kalau executor yang kamu pasang tidak benar-benar serial (atau bisa deadlock dengan dirinya), garansi actor pecah. DispatchSerialQueue aman; concurrent queue tidak.

Global Executors

Beberapa executor "tunggal" disediakan runtime:

  • MainActor.shared.unownedExecutor — executor di main thread.
  • GlobalActor apapun — executor yang ditentukan implementasinya.
  • Detached Task tanpa actor → cooperative thread pool.

Mengukur Executor Saat Runtime

Untuk debugging, kamu bisa memeriksa "di mana saya saat ini":

swift
@inlinable
func currentExecutorDescription() -> String {
    if Thread.isMainThread { return "main thread" }
    return "background (\(Thread.current.name ?? "unnamed"))"
}

actor Worker {
    func process() {
        print(currentExecutorDescription())  // background biasanya
    }
}