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!
}
}