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.
✨ 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
🧪 Well Tested - Comprehensive test coverage
- Requirements
- Installation
- Quick Start
- Usage
- Common Use Cases
- Communication
- Documentation
- Contributing
- Author
- License
| Platform | Minimum Version |
|---|---|
| iOS | 13.0+ |
| macOS | 10.15+ |
| tvOS | 13.0+ |
| watchOS | 6.0+ |
| visionOS | 1.0+ |
| Xcode | 15.3+ |
| Swift | 5.10+ |
Add the following dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/space-code/typhoon.git", from: "1.4.0")
]Or add it through Xcode:
- File > Add Package Dependencies
- Enter package URL:
https://github.com/space-code/typhoon.git - Select version requirements
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)")
}Typhoon provides three 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: Int, duration: DispatchTimeInterval)
/// A retry strategy with an exponential increase in duration between retries.
case exponential(retry: Int, multiplier: Double = 2.0, duration: DispatchTimeInterval)
/// A retry strategy with exponential increase in duration between retries and added jitter.
case exponentialWithJitter(
retry: Int,
jitterFactor: Double = 0.1,
maxInterval: DispatchTimeInterval? = .seconds(60),
multiplier: Double = 2.0,
duration: DispatchTimeInterval
)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, duration: .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
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,
multiplier: 2.0,
duration: .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²)
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: .exponentialWithJitter(
retry: 5,
jitterFactor: 0.2, // Add ±20% randomization
maxInterval: .seconds(30), // Cap at 30 seconds
multiplier: 2.0,
duration: .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
import Typhoon
class APIClient {
private let retryService = RetryPolicyService(
strategy: .exponential(retry: 3, duration: .milliseconds(500))
)
func fetchUser(id: String) async throws -> User {
try await retryService.retry {
let (data, _) = try await URLSession.shared.data(
from: URL(string: "https://api.example.com/users/\(id)")!
)
return try JSONDecoder().decode(User.self, from: data)
}
}
}import Typhoon
class DatabaseManager {
private let retryService = RetryPolicyService(
strategy: .exponentialWithJitter(
retry: 5,
jitterFactor: 0.15,
maxInterval: .seconds(60),
duration: .seconds(1)
)
)
func saveRecord(_ record: Record) async throws {
try await retryService.retry {
try await database.insert(record)
}
}
}import Typhoon
class FileService {
private let retryService = RetryPolicyService(
strategy: .constant(retry: 3, duration: .milliseconds(100))
)
func writeFile(data: Data, to path: String) async throws {
try await retryService.retry {
try data.write(to: URL(fileURLWithPath: path))
}
}
}import Typhoon
class PaymentService {
private let retryService = RetryPolicyService(
strategy: .exponential(
retry: 4,
multiplier: 1.5,
duration: .seconds(2)
)
)
func processPayment(amount: Decimal) async throws -> PaymentResult {
try await retryService.retry {
try await paymentGateway.charge(amount: amount)
}
}
}- 🐛 Found a bug? Open an issue
- 💡 Have a feature request? Open an issue
- ❓ Questions? Start a discussion
- 🔒 Security issue? Email nv3212@gmail.com
Comprehensive documentation is available: Typhoon Documentation
We love contributions! Please feel free to help out with this project. If you see something that could be made better or want a new feature, open up an issue or send a Pull Request.
Bootstrap the development environment:
mise installNikita Vasilev
- Email: nv3212@gmail.com
- GitHub: @ns-vasilev
Typhoon is released under the MIT license. See LICENSE for details.
Made with ❤️ by space-code
