Section 6/161 menit
6. Clock Protocol dan Time-based Concurrency
6. Clock Protocol dan Time-based Concurrency
Clock protocol (Swift 5.7) mengabstraksi "sumber waktu" — memungkinkan testing kode time-based secara deterministik.
Tiga Clock Bawaan Swift
swift
// ContinuousClock: monotonic, tidak pause saat device sleep
// Cocok untuk: latency measurement, timeout
let continuousClock = ContinuousClock()
let start = continuousClock.now
try await Task.sleep(until: start.advanced(by: .seconds(2)), clock: continuousClock)
// SuspendingClock: monotonic, PAUSE saat device sleep
// Cocok untuk: reminder, alarm, scheduler
let suspendingClock = SuspendingClock()
try await Task.sleep(until: suspendingClock.now.advanced(by: .minutes(30)), clock: suspendingClock)
// Singkatan yang lebih umum digunakan:
try await Task.sleep(for: .seconds(2)) // menggunakan ContinuousClock
try await Task.sleep(for: .seconds(2), tolerance: .milliseconds(100)) // dengan toleransi
// Measure execution time
let elapsed = ContinuousClock().measure {
// operasi yang di-ukur
Thread.sleep(forTimeInterval: 0.1)
}
print("Elapsed: \(elapsed)") // contoh: 0.1s
Generic Clock untuk Testing
swift
// Fungsi yang bisa di-test dengan clock arbitrary
func retryWithBackoff<C: Clock>(
clock: C,
maxAttempts: Int,
initialDelay: C.Duration,
operation: () async throws -> Void
) async throws where C.Duration == Duration {
var delay = initialDelay
for attempt in 1...maxAttempts {
do {
try await operation()
return
} catch {
guard attempt < maxAttempts else { throw error }
try await Task.sleep(for: delay, clock: clock)
delay = delay * 2 // exponential backoff
}
}
}
// Production: pakai ContinuousClock
try await retryWithBackoff(
clock: ContinuousClock(),
maxAttempts: 3,
initialDelay: .seconds(1)
) {
try await fetchFromServer()
}
// Testing: pakai TestClock dari swift-clocks package (community)
// TestClock memungkinkan advance waktu secara manual
// try await retryWithBackoff(
// clock: testClock,
// maxAttempts: 3,
// initialDelay: .seconds(1)
// ) { ... }
// await testClock.advance(by: .seconds(1)) // simulasi retry pertama
// await testClock.advance(by: .seconds(2)) // simulasi retry kedua
func fetchFromServer() async throws {}
Deadline Pattern
swift
// Timeout dengan deadline
func withTimeout<T, C: Clock>(
_ duration: C.Duration,
clock: C,
operation: () async throws -> T
) async throws -> T where C.Duration == Duration {
let deadline = clock.now.advanced(by: duration)
return try await withThrowingTaskGroup(of: T.self) { group in
group.addTask {
try await operation()
}
group.addTask {
try await Task.sleep(until: deadline, clock: clock)
throw TimeoutError()
}
// Ambil hasil pertama yang selesai
defer { group.cancelAll() }
guard let result = try await group.next() else {
throw TimeoutError()
}
return result
}
}
struct TimeoutError: LocalizedError {
var errorDescription: String? { "Operation timed out" }
}
// Penggunaan
let result = try await withTimeout(.seconds(5), clock: ContinuousClock()) {
try await fetchLargeDataSet()
}
func fetchLargeDataSet() async throws -> Data { Data() }