Section 10/161 menit

10. Custom Global Actors

10. Custom Global Actors

Global actor adalah singleton actor yang bisa di-annotate ke tipe dan method — mirip @MainActor tapi custom.

Membuat Global Actor

swift
// Custom global actor untuk database access
@globalActor
actor DatabaseActor {
    static let shared = DatabaseActor()

    // Isolasi database access ke satu thread
    private let queue = DispatchQueue(label: "database", qos: .userInitiated)

    nonisolated var unownedExecutor: UnownedSerialExecutor {
        DatabaseExecutor.shared.asUnownedSerialExecutor()
    }
}

final class DatabaseExecutor: SerialExecutor, @unchecked Sendable {
    static let shared = DatabaseExecutor()
    private let queue = DispatchQueue(label: "database.executor", qos: .userInitiated)

    func enqueue(_ job: consuming ExecutorJob) {
        let unownedJob = UnownedJob(job)
        queue.async {
            unownedJob.runSynchronously(on: self.asUnownedSerialExecutor())
        }
    }

    func asUnownedSerialExecutor() -> UnownedSerialExecutor {
        UnownedSerialExecutor(ordinary: self)
    }
}

// Annotate class dengan custom global actor
@DatabaseActor
final class UserRepository {
    private var db: DatabaseConnection

    init(db: DatabaseConnection) {
        self.db = db
    }

    // Method ini otomatis berjalan di DatabaseActor
    func fetchUser(id: String) async throws -> User {
        try db.query("SELECT * FROM users WHERE id = ?", id)
    }

    func saveUser(_ user: User) async throws {
        try db.execute("INSERT INTO users ...", user)
    }
}

// Bisa juga annotate method individual
class DataService {
    @DatabaseActor
    func performDatabaseOperation() async {
        // Method ini berjalan di DatabaseActor meski class-nya tidak di-annotate
    }

    @MainActor
    func updateUI() {
        // Berjalan di main actor
    }

    func bridgeBetweenActors() async {
        await performDatabaseOperation()  // hop ke DatabaseActor
        await updateUI()                  // hop ke MainActor
    }
}

class DatabaseConnection {
    func query<T>(_ sql: String, _ params: Any...) throws -> T { fatalError() }
    func execute(_ sql: String, _ params: Any...) throws {}
}