Section 5/161 menit

5. Sendable Protocol — Deep Dive

5. Sendable Protocol — Deep Dive

Hierarki Sendability

swift
// 1. Struct dengan semua stored properties Sendable → otomatis Sendable
struct User: Sendable {
    let id: UUID         // Sendable
    let name: String     // Sendable
    let age: Int         // Sendable
}

// 2. Enum dengan semua associated values Sendable → otomatis Sendable
enum Result<T: Sendable>: Sendable {
    case success(T)
    case failure(Error)  // Error adalah Sendable (existential)
}

// 3. Class harus explicit — dan butuh internal synchronization
final class SendableCounter: Sendable {
    // Harus final agar tidak ada subclass yang break invariant
    private let lock = NSLock()
    private var _count: Int = 0
    
    var count: Int {
        lock.withLock { _count }
    }
    
    func increment() {
        lock.withLock { _count += 1 }
    }
}

// Atau gunakan actor (lebih idiomatis)
actor Counter {
    private var count: Int = 0
    
    func increment() { count += 1 }
    func value() -> Int { count }
}

@unchecked Sendable — Escape Hatch

swift
// Gunakan @unchecked Sendable hanya jika:
// 1. Kamu yakin synchronization sudah benar secara manual
// 2. Type dari external library yang kamu tahu thread-safe
// 3. Wrapping C/ObjC type yang memiliki documented thread safety

final class ThreadSafeQueue<Element>: @unchecked Sendable {
    private var storage: [Element] = []
    private let lock = NSLock()
    
    func enqueue(_ element: Element) {
        lock.withLock { storage.append(element) }
    }
    
    func dequeue() -> Element? {
        lock.withLock {
            guard !storage.isEmpty else { return nil }
            return storage.removeFirst()
        }
    }
}

// Wrapping DispatchQueue sebagai @unchecked Sendable
// DispatchQueue sendiri Sendable, tapi closure yang di-submit tidak
extension DispatchQueue: @unchecked Sendable { }  // sudah built-in

Sendable Closure

swift
// @Sendable closure — dapat dieksekusi di isolation domain yang berbeda
typealias SendableWork = @Sendable () async -> Void

func scheduleWork(_ work: @Sendable @escaping () async -> Void) {
    Task.detached(operation: work)
}

// Non-Sendable closure — hanya bisa dieksekusi di domain yang sama
func scheduleLocal(_ work: () async -> Void) {
    // ❌ Tidak bisa di-pass ke Task.detached — bukan Sendable
    Task { await work() }  // ✓ Task inherit isolation, aman
}

// Practical example: background processing
actor ImageProcessor {
    func process(
        imageData: Data,
        completion: @Sendable @escaping (Data) -> Void
    ) async {
        let result = performHeavyProcessing(imageData)
        completion(result)  // dipanggil dari actor context
    }
}

Conditional Sendable

swift
// Generic type yang Sendable hanya jika type parameter-nya Sendable
struct Box<T> {
    let value: T
}

// Otomatis: Box<Int> adalah Sendable, Box<NonSendableClass> tidak
extension Box: Sendable where T: Sendable { }

// Verifikasi
let intBox = Box(value: 42)
let stringBox = Box(value: "hello")
// intBox dan stringBox: Sendable ✓

class NotSendable { var x = 0 }
let classBox = Box(value: NotSendable())
// classBox: tidak Sendable — karena NotSendable bukan Sendable