Repository navigation
.package(url: "https://github.com/Toni77777/SafeRetry.git", from: "1.0.0")The first release. Everything below is new; the history of the work that led
up to it is in the git log.
Added
Policies
RetryPolicy— aSendablevalue type that runs an operation up to
maxAttemptstimes, the first call included, pausing between attempts
according to aDelayStrategyand aJitterStrategy. AshouldRetry
predicate receives the error and the 1-based attempt index. Without one,
every error exceptCircuitBreakerErroris retried, so a retry around an
open breaker gives up at the first rejection instead of waiting out its
backoff; a predicate you pass replaces that default.CircuitBreaker— an actor implementing closed / open / half-open with a
rolling failure window:failureThreshold,successThreshold,
halfOpenMaxCalls,timeoutandrollingWindow, plus anisFailure
predicate for errors that are the caller's problem rather than the
dependency's. It rejects withCircuitBreakerError.open(retryAfter:)or
.halfOpenRejected.statereports.halfOpenas soon as the open window
has elapsed, andreset()forces the circuit closed.TimeoutPolicy— races the operation against a timer in a task group and
throwsTimeoutErroronce the deadline passes, cancelling the operation.FallbackPolicy— returns a constant value, or the result of a handler
that may itself throw, when the operation fails.
Strategies
DelayStrategy—.immediate,.fixed,.linear,.exponential(with
amultiplierand amaxDelaycap) and.custom. Computed delays never go
negative and never overflowDuration.JitterStrategy—.none,.fulland.equal.
Composition
PolicyChain— a fluent builder that nests retry, circuit breaker and
timeout outermost-first.retry(_:)andtimeout(_:)also accept a
configured policy, soonEventcallbacks work inside a chain.FallbackChain— a chain terminated by.fallback(_:onEvent:)or
.fallback(onEvent:_:). It throws only on cancellation or when the handler
throws.PolicyChain.Preset—.default(3 attempts, exponential backoff from
1 s),.aggressive(10 attempts, from 500 ms) and.conservative(2 attempts
2 s apart, each bounded by a 5 s timeout).
Registry
PolicyRegistry— an actor that stores chains, circuit breakers and
retry policies by name. Lookups throwRegistryError.notFound(name:)or
.typeMismatch(name:expected:found:);unregister(_:),unregisterAll()
andregisteredNamesmanage the contents. A process-wide instance is
available asSafeRetry.registry.
Observability
PolicyEvent— nine events covering attempts, retries, circuit state,
rejections, timeouts and fallbacks, delivered synchronously through each
policy'sonEventcallback in the order they occur.CircuitState—.closed,.open(until:)and.halfOpen.EventStream— a single-consumerAsyncSequencewith synchronous
emit(_:)andfinish(), so producers inside actors publish without
suspending. It buffers the newest 1024 events by default.
Errors
CircuitBreakerError(with a uniformretryAfter),TimeoutError,
ServerError(the HTTP 5xx status code) andRegistryError. All four are
Sendable,EquatableandLocalizedError, and the structs have public
initialisers for use in predicates, mocks and tests.
URLSession
data(for:retrying:)retries only transient failures — sixURLErrorcodes
and HTTP 5xx, mapped toServerError.data(for:resilience:)runs the
request through anyPolicyChain. Both return 4xx responses untouched.
Namespace
SafeRetry.retry(attempts:delay:_:)andSafeRetry.withCircuitBreaker(_:_:)
for one-off calls.
Guarantees
- Cancellation is never a failure.
CancellationErrorand
URLError(.cancelled)are treated alike: never retried, never counted by a
breaker, never replaced by a fallback, and they emit no event. Every policy
checks for cancellation before running the operation. - Swift 6 strict concurrency with no
@unchecked Sendableor
nonisolated(unsafe)in the library, and no dependencies. - Requires Swift 6.4 (Xcode 27); supports iOS 16, macOS 13, tvOS 16 and
watchOS 9 or later.
Documentation
- A DocC catalogue with Getting Started, Observability and Migrating from
RxSwift articles, and DocC comments on every public symbol. The test suite
mirrors the README and DocC examples, so an API change that breaks one fails
the build.