Section 6/131 menit

6. Continuation

6. Continuation

Teori: Jembatan dari Callback ke Async World

Banyak API Apple dan library pihak ketiga masih menggunakan completion handler. Continuation memungkinkan kita membungkus API tersebut menjadi async throws function tanpa mengubah library aslinya.

swift
Kode async (await) ←──── Continuation ────→ Callback API

withCheckedContinuation — Aman dengan Runtime Check

swift
// Membungkus URLSession dataTask (cara lama) menjadi async
func fetchDataLegacy(from url: URL) async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        URLSession.shared.dataTask(with: url) { data, response, error in
            // Resume HARUS dipanggil tepat satu kali
            if let error = error {
                continuation.resume(throwing: error)
                return
            }

            guard let data = data else {
                continuation.resume(throwing: NetworkError.noData)
                return
            }

            continuation.resume(returning: data)
            // Setelah ini jangan panggil continuation lagi!
        }.resume()
    }
}

// Membungkus operasi yang tidak bisa gagal
func requestPermission() async -> Bool {
    await withCheckedContinuation { continuation in
        PHPhotoLibrary.requestAuthorization { status in
            continuation.resume(returning: status == .authorized)
        }
    }
}

Aturan Continuation:

  • resume HARUS dipanggil tepat 1 kali
  • Memanggil 0 kali → task leak (hang selamanya)
  • Memanggil 2+ kali → crash (withCheckedContinuation akan crash, withUnsafeContinuation undefined behavior)

AsyncStream dari Delegate

Untuk API yang memanggil callback berulang kali (delegate, NotificationCenter):

swift
// CLLocationManager → AsyncStream
class LocationService {
    private let locationManager = CLLocationManager()

    // Expose lokasi sebagai AsyncStream
    var locationStream: AsyncStream<CLLocation> {
        AsyncStream { continuation in
            let delegate = LocationDelegate { location in
                continuation.yield(location)
            }

            continuation.onTermination = { _ in
                // Cleanup saat stream dihentikan
                delegate.stopUpdates()
            }

            locationManager.delegate = delegate
            locationManager.startUpdatingLocation()
        }
    }
}

// Penggunaan
let locationService = LocationService()

Task {
    for await location in locationService.locationStream {
        print("Lokasi baru: \(location.coordinate)")

        // Stream otomatis berhenti saat Task di-cancel
        if shouldStop {
            break
        }
    }
}
swift
// NotificationCenter → AsyncStream
extension NotificationCenter {
    func notifications(named name: Notification.Name,
                       object: AnyObject? = nil) -> AsyncStream<Notification> {
        AsyncStream { continuation in
            let observer = addObserver(forName: name, object: object, queue: nil) { notification in
                continuation.yield(notification)
            }

            continuation.onTermination = { _ in
                self.removeObserver(observer)
            }
        }
    }
}

// Penggunaan
Task {
    for await _ in NotificationCenter.default.notifications(named: UIApplication.didBecomeActiveNotification) {
        await refreshData()
    }
}