Pre-1.0 Notice: This library is under active development. The API may change between minor versions until 1.0.
A robust, protocol-oriented retry library for Swift 6 with comprehensive backoff strategies, jitter support, and circuit breaker patterns.
- Pure Swift 6 with strict concurrency checking
- Sendable and thread-safe throughout
- Multiple retry strategies: Constant, Linear, Exponential, Fibonacci
- Jitter support: Full, Equal, Decorrelated, and None
- Circuit Breaker pattern to prevent cascading failures
- Flexible configuration with predicates and timeouts
- No external dependencies (except swift-docc-plugin)
- Comprehensive test coverage
- iOS 15+
- macOS 12+
- tvOS 15+
- watchOS 8+
- visionOS 1+
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/CorvidLabs/swift-retry.git", from: "0.1.0")
]import Retry
let result = try await Retry.execute(maxAttempts: 3) {
try await fetchData()
}let result = try await Retry.execute(
maxAttempts: 5,
strategy: .exponential(base: 1.0, multiplier: 2.0),
jitter: .full
) {
try await networkRequest()
}let breaker = CircuitBreaker(failureThreshold: 5, resetTimeout: 60.0)
let result = try await Retry.execute(
maxAttempts: 3,
strategy: .fibonacci(base: 1.0),
circuitBreaker: breaker
) {
try await externalAPICall()
}let config = RetryConfiguration(
maxAttempts: 5,
maxDelay: 30.0,
timeout: 120.0,
shouldRetry: { error in
// Only retry network errors
return error is URLError
}
)
let result = try await Retry.execute(
configuration: config,
strategy: .exponential(base: 2.0),
jitter: .decorrelated()
) {
try await operation()
}Fixed delay between retries:
.constant(2.0) // 2 second delay each timeLinearly increasing delay:
.linear(base: 1.0, increment: 0.5)
// Delays: 1.0, 1.5, 2.0, 2.5...Exponential backoff:
.exponential(base: 1.0, multiplier: 2.0)
// Delays: 1.0, 2.0, 4.0, 8.0...Fibonacci sequence delays:
.fibonacci(base: 1.0)
// Delays: 1.0, 1.0, 2.0, 3.0, 5.0, 8.0...Uses exact delay from strategy:
jitter: .noneRandom delay between 0 and calculated delay:
jitter: .fullHalf delay + random half:
jitter: .equalAWS-style decorrelated jitter:
jitter: .decorrelated(base: 1.0)Prevent cascading failures by opening the circuit after a threshold:
let breaker = CircuitBreaker(
failureThreshold: 5, // Open after 5 failures
resetTimeout: 60.0 // Try again after 60 seconds
)
// Use with retry
let result = try await Retry.execute(
maxAttempts: 3,
circuitBreaker: breaker
) {
try await operation()
}
// Check state
let state = await breaker.currentState // .closed, .open, or .halfOpen
// Reset manually if needed
await breaker.reset()The library provides specific error types:
do {
let result = try await Retry.execute(maxAttempts: 3) {
try await operation()
}
} catch RetryError.maxAttemptsExceeded(let attempts, let lastError) {
print("Failed after \(attempts) attempts: \(lastError)")
} catch RetryError.timeout(let duration) {
print("Timed out after \(duration) seconds")
} catch RetryError.circuitBreakerOpen {
print("Circuit breaker is open")
} catch {
print("Operation failed: \(error)")
}For non-throwing contexts:
let result = await Retry.executeReturningResult(
maxAttempts: 3,
strategy: .exponential(base: 1.0)
) {
try await operation()
}
switch result {
case .success(let value):
print("Success: \(value)")
case .failure(let error):
print("Failed: \(error)")
}// Default: 3 attempts, no limits
.default
// Conservative: 5 attempts with timeouts
.conservative
// Aggressive: 10 attempts with longer timeouts
.aggressiveenum APIError: Error, Equatable {
case rateLimit
case serverError
case badRequest
}
let config = RetryConfiguration.forErrors(
maxAttempts: 5,
retryableErrors: Set([APIError.rateLimit, APIError.serverError])
)
// Only retries on rateLimit and serverError
try await Retry.execute(configuration: config, strategy: .exponential(base: 2.0)) {
try await apiCall()
}This library follows protocol-oriented design principles:
- Protocols over classes:
RetryStrategyandJitterare protocols - Value types: Strategies and configurations are structs
- Composition: Mix and match strategies, jitter, and circuit breakers
- Type safety: Strong typing prevents runtime errors
- Sendable: Safe for concurrent use with async/await
- Clean API: Static member syntax for common cases
Run tests with:
swift testBuild the package:
swift buildMIT
Contributions welcome! Please ensure:
- Swift 6 compatibility
- Sendable conformance
- Comprehensive tests
- Documentation for public APIs