Small async retry primitives for Swift with explicit attempts and terminal cancellation.
Features · Installation · Quick Start · When To Use · Good Fits · Weaker Fits · Runtime Semantics · Documentation · Testing
- async-first retry entry point with
Retry { ... } - explicit
.maxAttempts(_:)configuration - fixed delay support through
.delay(_:) - exponential backoff and bounded jitter
- retry predicates through
.retry { ... } - terminal cancellation that is not retried
- small surface area intended to grow in layers
The current public API is intentionally centered on:
Retry
Add ResilienceKit to your Swift Package Manager dependencies:
dependencies: [
.package(url: "https://github.com/AltiAntonov/ResilienceKit.git", from: "0.4.0")
]Then add the product to your target:
.target(
name: "YourApp",
dependencies: [
.product(name: "ResilienceKit", package: "ResilienceKit")
]
)import ResilienceKit
let value = try await Retry {
try await fetchProfile()
}
.maxAttempts(4)
.exponentialBackoff(
baseDelay: .milliseconds(250),
multiplier: 2,
maxDelay: .seconds(5),
jitter: .fraction(0.2)
)
.retry { error in
error is URLError
}
.run()maxAttempts(_:) always means total executions, including the first call.
Use ResilienceKit when you want retry behavior to be explicit, readable, and reusable instead of re-implementing ad hoc retry loops around async work.
It is a strong fit when the first thing you need is a small retry primitive, not a full resilience framework.
- app and SDK code that wraps async network requests
- codebases that want one obvious retry call site instead of repeated
forloops - teams that want retry, fixed delay, backoff, and jitter behind one call-site shape
- code that needs to retry transient errors while failing fast on permanent errors
- small packages or apps that want focused retry behavior without unrelated dependencies
- projects that need HTTP-specific retry helpers today
- systems that already require a broader resilience stack such as circuit breaking or rate limiting
- sync-only code paths
- packages that need broad platform coverage below iOS 17 or macOS 14 right now
.maxAttempts(_:)controls the total number of attempts, not retries-after-the-first- values below
1are clamped to1 .delay(_:)configures a fixed delay between failed attempts and defaults to.zero.exponentialBackoff(baseDelay:multiplier:maxDelay:jitter:)configures growing retry delaysRetryJitter.fraction(_:)applies bounded randomness to reduce synchronized retries.retry { ... }decides whether a non-cancellation error is retryable- the first attempt always starts immediately
- delay is applied only between eligible retries, never after the final failed attempt
- non-retryable errors are thrown immediately without delay or another attempt
- cancellation before the first attempt prevents the operation from running
CancellationErroris terminal and is rethrown without additional attempts- cancellation during delay is rethrown and no later attempt runs
- all non-cancellation thrown errors are retried until attempts are exhausted
HTTP-specific retry helpers are intentionally deferred to later releases.
The package now includes a DocC catalog in Sources/ResilienceKit/ResilienceKit.docc.
Once Swift Package Index processes .spi.yml, hosted documentation should appear on the package page automatically.
The package uses Swift Testing.
Current coverage verifies:
- first-attempt success
- retry-until-success behavior
- fixed delay between failed attempts
- exponential backoff progression
- bounded jitter behavior
- selective retry behavior
- fail-fast behavior for non-retryable errors
- no trailing delay after the final failed attempt
- exact call count on persistent failure
- clamp behavior for invalid attempt counts
- terminal cancellation from both pre-cancelled tasks, thrown
CancellationError, and cancellation during delay