A powerful retry policy service for Swift
License Swift Compatibility Platform Compatibility CI
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 Γγ°γ€ 20)
- Attempt 3: After 2 seconds (1 Γγ°γ€ 21)
- Attempt 4: After 4 seconds (1 Γγ°γ€ 22)
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 install
Nikita Vasilev
- Email: nv3212@gmail.com
- GitHub: @ns-vasilev
Typhoon is released under the MIT license. See LICENSE for details.
Made with β€οΈ by space-code