Section 9/161 menit

9. Continuation Edge Cases dan UnsafeContinuation

9. Continuation Edge Cases dan UnsafeContinuation

Rules of Continuation

Continuation harus di-resume tepat satu kali. Resume lebih dari sekali adalah undefined behavior; tidak resume sama sekali menyebabkan task leak.

swift
// CheckedContinuation: produksi error jika aturan dilanggar
func fetchData() async throws -> Data {
    return try await withCheckedThrowingContinuation { continuation in
        legacyNetworkCall { result in
            switch result {
            case .success(let data):
                continuation.resume(returning: data)
                // ⚠️ JANGAN: continuation.resume(returning: data) lagi
                // CheckedContinuation akan trap jika di-resume dua kali
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
    }
}

func legacyNetworkCall(completion: @escaping (Result<Data, Error>) -> Void) {}

UnsafeContinuation untuk Performa Kritis

UnsafeContinuation skip runtime checks — lebih cepat tapi berbahaya jika salah pakai:

swift
// UnsafeContinuation: gunakan HANYA jika performa sangat kritis
// dan Anda YAKIN continuation di-resume tepat sekali
func highFrequencyCallback() async throws -> SensorReading {
    return try await withUnsafeThrowingContinuation { continuation in
        // Path ini dipanggil 1000x per detik — overhead CheckedContinuation terasa
        SensorManager.registerOneShot { reading in
            // KRITIS: pastikan ini hanya dipanggil sekali
            // Tidak ada safety net — bug langsung crash/undefined behavior
            continuation.resume(returning: reading)
        }
    }
}

struct SensorReading { let value: Double }
enum SensorManager { static func registerOneShot(callback: @escaping (SensorReading) -> Void) {} }

Pattern Aman: Wrapper dengan Cancellation

swift
// Pattern aman untuk delegate-based API dengan cancellation support
final class DelegateWrapper<T: Sendable>: NSObject, @unchecked Sendable {
    private var continuation: CheckedContinuation<T, Error>?
    private let lock = NSLock()
    private var hasResumed = false  // guard terhadap double resume

    func setResult(_ value: T) {
        lock.withLock {
            guard !hasResumed else { return }
            hasResumed = true
            continuation?.resume(returning: value)
            continuation = nil
        }
    }

    func setError(_ error: Error) {
        lock.withLock {
            guard !hasResumed else { return }
            hasResumed = true
            continuation?.resume(throwing: error)
            continuation = nil
        }
    }

    func wait() async throws -> T {
        try await withTaskCancellationHandler {
            try await withCheckedThrowingContinuation { cont in
                lock.withLock { self.continuation = cont }
            }
        } onCancel: {
            setError(CancellationError())
        }
    }
}

withTaskCancellationHandler

Untuk membersihkan resource saat task di-cancel:

swift
// Integrasi operasi yang tidak bisa di-cancel natively
func download(from url: URL) async throws -> Data {
    let sessionTask = URLSession.shared.dataTask(with: url)

    return try await withTaskCancellationHandler {
        // Operasi utama
        try await withCheckedThrowingContinuation { continuation in
            sessionTask.resume()
            // URLSessionTask tidak langsung async — wrap dengan continuation
        }
    } onCancel: {
        // Dipanggil dari arbitrary thread saat Task di-cancel
        sessionTask.cancel()
        // TIDAK boleh mengakses actor-isolated state di sini tanpa synchronization!
    }
}