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() }