Typhoon
Typhoon is a lightweight library for handling task retries with customizable policies.
Install / Use
/learn @space-code/TyphoonREADME

Description
Typhoon is a modern, lightweight Swift framework that provides elegant and robust retry policies for asynchronous operations. Built with Swift's async/await concurrency model, it helps you handle transient failures gracefully with configurable retry strategies.
Features
✨ Multiple Retry Strategies - Constant, exponential, and exponential with jitter
⚡ Async/Await Native - Built for modern Swift concurrency
🎯 Type-Safe - Leverages Swift's type system for compile-time safety
🔧 Configurable - Flexible retry parameters for any use case
📱 Cross-Platform - Works on iOS, macOS, tvOS, watchOS, and visionOS
⚡ Lightweight - Minimal footprint with zero dependencies
🧾 Pluggable Logging – Integrates with OSLog or custom loggers
🌐 URLSession Integration – Retry network requests with a single parameter
🧪 Well Tested - Comprehensive test coverage
Table of Contents
- Requirements
- Installation
- Quick Start
- Usage
- Logging
- URLSession Integration
- Common Use Cases
- Communication
- Documentation
- Contributing
- Author
- License
Requirements
| Platform | Minimum Version | |-----------|----------------| | iOS | 13.0+ | | macOS | 10.15+ | | tvOS | 13.0+ | | watchOS | 6.0+ | | visionOS | 1.0+ | | Xcode | 15.3+ | | Swift | 5.10+ |
Installation
Swift Package Manager
Add the following dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/space-code/typhoon.git", from: "2.0.0")
]
Or add it through Xcode:
- File > Add Package Dependencies
- Enter package URL:
https://github.com/space-code/typhoon.git - Select version requirements
Quick Start
import Typhoon
let retryService = RetryPolicyService(
strategy: .constant(retry: 3, duration: .seconds(1))
)
do {
let result = try await retryService.retry {
try await fetchDataFromAPI()
}
print("✅ Success: \(result)")
} catch {
print("❌ Failed after retries: \(error)")
}
Usage
Retry Strategies
Typhoon provides six powerful retry strategies to handle different failure scenarios:
/// A retry strategy with a constant number of attempts and fixed duration between retries.
case constant(retry: UInt, dispatchDuration: DispatchTimeInterval)
/// A retry strategy with a linearly increasing delay.
case linear(retry: UInt, dispatchDuration: DispatchTimeInterval)
/// A retry strategy with a Fibonacci-based delay progression.
case fibonacci(retry: UInt, dispatchDuration: DispatchTimeInterval)
/// A retry strategy with exponential increase in duration between retries and added jitter.
case exponential(
retry: UInt,
jitterFactor: Double = 0.1,
maxInterval: DispatchTimeInterval? = .seconds(60),
multiplier: Double = 2.0,
dispatchDuration: DispatchTimeInterval
)
/// A custom retry strategy defined by a user-provided delay calculator.
case custom(retry: UInt, strategy: IRetryDelayStrategy)
Additionally, Typhoon allows composing multiple retry strategies into a single policy using a chained strategy:
RetryPolicyStrategy.chain([
.constant(retry: 2, dispatchDuration: .seconds(1)),
.exponential(retry: 3, dispatchDuration: .seconds(2))
])
Constant Strategy
Best for scenarios where you want predictable, fixed delays between retries:
import Typhoon
// Retry up to 5 times with 2 seconds between each attempt
let service = RetryPolicyService(
strategy: .constant(retry: 4, dispatchDuration: .seconds(2))
)
do {
let data = try await service.retry {
try await URLSession.shared.data(from: url)
}
} catch {
print("Failed after 5 attempts")
}
Retry Timeline:
- Attempt 1: Immediate
- Attempt 2: After 2 seconds
- Attempt 3: After 2 seconds
- Attempt 4: After 2 seconds
- Attempt 5: After 2 seconds
Linear Strategy
Delays grow proportionally with each attempt — a middle ground between constant and exponential:
import Typhoon
// Retry up to 4 times with linearly increasing delays
let service = RetryPolicyService(
strategy: .linear(retry: 3, dispatchDuration: .seconds(1))
)
Retry Timeline:
- Attempt 1: Immediate
- Attempt 2: After 1 second (1 × 1)
- Attempt 3: After 2 seconds (1 × 2)
- Attempt 4: After 3 seconds (1 × 3)
Fibonacci Strategy
Delays follow the Fibonacci sequence — grows faster than linear but slower than exponential:
import Typhoon
let service = RetryPolicyService(
strategy: .fibonacci(retry: 5, dispatchDuration: .seconds(1))
)
Retry Timeline:
- Attempt 1: Immediate
- Attempt 2: After 1 second
- Attempt 3: After 1 second
- Attempt 4: After 2 seconds
- Attempt 5: After 3 seconds
- Attempt 6: After 5 seconds
Exponential Strategy
Ideal for avoiding overwhelming a failing service by progressively increasing wait times:
import Typhoon
// Retry up to 4 times with exponentially increasing delays
let service = RetryPolicyService(
strategy: .exponential(
retry: 3,
jitterFactor: 0,
multiplier: 2.0,
dispatchDuration: .seconds(1)
)
)
do {
let response = try await service.retry {
try await performNetworkRequest()
}
} catch {
print("Request failed after exponential backoff")
}
Retry Timeline:
- Attempt 1: Immediate
- Attempt 2: After 1 second (1 × 2⁰)
- Attempt 3: After 2 seconds (1 × 2¹)
- Attempt 4: After 4 seconds (1 × 2²)
Exponential with Jitter Strategy
The most sophisticated strategy, adding randomization to prevent thundering herd problems:
import Typhoon
// Retry with exponential backoff, jitter, and maximum interval cap
let service = RetryPolicyService(
strategy: .exponential(
retry: 5,
jitterFactor: 0.2, // Add ±20% randomization
maxInterval: .seconds(30), // Cap at 30 seconds
multiplier: 2.0,
dispatchDuration: .seconds(1)
)
)
do {
let result = try await service.retry {
try await connectToDatabase()
}
} catch {
print("Connection failed after sophisticated retry attempts")
}
Benefits of Jitter:
- Prevents multiple clients from retrying simultaneously
- Reduces load spikes on recovering services
- Improves overall system resilience
Custom Strategy
Provide your own delay logic by implementing IRetryDelayStrategy:
import Typhoon
struct QuadraticDelayStrategy: IRetryDelayStrategy {
func delay(forRetry retries: UInt) -> UInt64? {
let seconds = Double(retries * retries) // 0s, 1s, 4s, 9s...
return UInt64(seconds * 1_000_000_000)
}
}
let service = RetryPolicyService(
strategy: .custom(retry: 4, strategy: QuadraticDelayStrategy())
)
Chain Strategy
Combines multiple strategies executed sequentially. Each strategy runs independently with its own delay logic, making it ideal for phased retry approaches — e.g. react quickly first, then back off gradually.
import Typhoon
let service = RetryPolicyService(
strategy: .chain([
// Phase 1: 3 quick attempts with constant delay
.init(retries: 3, strategy: ConstantDelayStrategy(dispatchDuration: .milliseconds(100))),
// Phase 2: 3 slower attempts with exponential backoff
.init(retries: 3, strategy: ExponentialDelayStrategy(
dispatchDuration: .seconds(1),
multiplier: 2.0,
jitterFactor: 0.1,
maxInterval: .seconds(60)
))
])
)
do {
let result = try await service.retry {
try await fetchDataFromAPI()
}
} catch {
print("Failed after all phases")
}
Retry Timeline:
Attempt 1: immediate
Attempt 2: 100ms ┐
Attempt 3: 100ms ├─ Phase 1: Constant
Attempt 4: 100ms ┘
Attempt 5: 1s ┐
Attempt 6: 2s ├─ Phase 2: Exponential
Attempt 7: 4s ┘
The total retry count is calculated automatically from the sum of all entries — no need to specify it manually.
Each strategy in the chain uses **
