@@ -7,21 +7,54 @@ import Foundation
77
88// MARK: - RetryIterator
99
10+ /// An iterator that generates retry delays according to a retry policy strategy.
11+ ///
12+ /// `RetryIterator` conforms to `IteratorProtocol` and produces a sequence of delay
13+ /// values (in nanoseconds) that can be used to schedule retry attempts for
14+ /// asynchronous operations such as network requests or background tasks.
15+ ///
16+ /// Each call to `next()` returns the delay for the current retry attempt and then
17+ /// advances the internal retry counter. When the maximum number of retries defined
18+ /// by the strategy is reached, the iterator stops producing values.
1019struct RetryIterator : IteratorProtocol {
1120 // MARK: Properties
1221
22+ /// The current retry attempt index.
23+ ///
24+ /// Starts from `0` and is incremented after each successful call to `next()`.
25+ /// This value is used when calculating exponential backoff delays.
1326 private var retries : UInt = 0
1427
28+ /// The retry policy strategy that defines:
29+ /// - The maximum number of retry attempts.
30+ /// - The algorithm used to calculate delays between retries
31+ /// (constant, exponential, or exponential with jitter).
1532 private let strategy : RetryPolicyStrategy
1633
1734 // MARK: Initialization
1835
36+ /// Creates a new `RetryIterator` with the specified retry policy strategy.
37+ ///
38+ /// - Parameter strategy: A `RetryPolicyStrategy` describing how retry delays
39+ /// should be calculated and how many retries are allowed.
1940 init ( strategy: RetryPolicyStrategy ) {
2041 self . strategy = strategy
2142 }
2243
2344 // MARK: IteratorProtocol
2445
46+ /// Returns the delay for the next retry attempt.
47+ ///
48+ /// The delay is calculated according to the current retry policy strategy
49+ /// and expressed in **nanoseconds**, making it suitable for use with
50+ /// `DispatchQueue`, `Task.sleep`, or other low-level scheduling APIs.
51+ ///
52+ /// After the delay is calculated, the internal retry counter is incremented.
53+ /// When the maximum number of retries is exceeded, this method returns `nil`,
54+ /// signaling the end of the iteration.
55+ ///
56+ /// - Returns: The delay in nanoseconds for the current retry attempt,
57+ /// or `nil` if no more retries are allowed.
2558 mutating func next( ) -> UInt64 ? {
2659 guard isValid ( ) else { return nil }
2760
@@ -32,10 +65,22 @@ struct RetryIterator: IteratorProtocol {
3265
3366 // MARK: Private
3467
68+ /// Determines whether another retry attempt is allowed.
69+ ///
70+ /// This method compares the current retry count with the maximum
71+ /// number of retries defined in the retry strategy.
72+ ///
73+ /// - Returns: `true` if another retry attempt is allowed;
74+ /// `false` otherwise.
3575 private func isValid( ) -> Bool {
3676 retries < strategy. retries
3777 }
3878
79+ /// Calculates the delay for the current retry attempt
80+ /// based on the selected retry strategy.
81+ ///
82+ /// - Returns: The computed delay in nanoseconds, or `0`
83+ /// if the duration cannot be converted to seconds.
3984 private func delay( ) -> UInt64 ? {
4085 switch strategy {
4186 case let . constant( _, duration) :
@@ -61,11 +106,26 @@ struct RetryIterator: IteratorProtocol {
61106
62107 // MARK: - Helper Methods
63108
109+ /// Converts a `DispatchTimeInterval` to nanoseconds.
110+ ///
111+ /// - Parameter duration: The time interval to convert.
112+ /// - Returns: The equivalent duration in nanoseconds, or `0`
113+ /// if the interval cannot be represented as seconds.
64114 private func convertToNanoseconds( _ duration: DispatchTimeInterval ) -> UInt64 ? {
65115 guard let seconds = duration. double else { return . zero }
66116 return safeConvertToUInt64 ( seconds * . nanosec)
67117 }
68118
119+ /// Calculates an exponential backoff delay without jitter.
120+ ///
121+ /// The delay is calculated as:
122+ /// `baseDelay * multiplier ^ retries`
123+ ///
124+ /// - Parameters:
125+ /// - duration: The base delay value.
126+ /// - multiplier: The exponential growth multiplier.
127+ /// - retries: The current retry attempt index.
128+ /// - Returns: The calculated delay in nanoseconds.
69129 private func calculateExponentialDelay(
70130 duration: DispatchTimeInterval ,
71131 multiplier: Double ,
@@ -79,6 +139,20 @@ struct RetryIterator: IteratorProtocol {
79139 return safeConvertToUInt64 ( value)
80140 }
81141
142+ /// Calculates an exponential backoff delay with jitter and an optional maximum interval.
143+ ///
144+ /// This method:
145+ /// 1. Calculates the exponential backoff delay.
146+ /// 2. Applies a random jitter to spread retry attempts over time.
147+ /// 3. Caps the result at the provided maximum interval, if any.
148+ ///
149+ /// - Parameters:
150+ /// - duration: The base delay value.
151+ /// - multiplier: The exponential growth multiplier.
152+ /// - retries: The current retry attempt index.
153+ /// - jitterFactor: The percentage of randomness applied to the delay.
154+ /// - maxInterval: An optional upper bound for the delay.
155+ /// - Returns: The final delay in nanoseconds.
82156 private func calculateExponentialDelayWithJitter(
83157 duration: DispatchTimeInterval ,
84158 multiplier: Double ,
@@ -107,6 +181,10 @@ struct RetryIterator: IteratorProtocol {
107181 return safeConvertToUInt64 ( min ( delayWithJitter, maxDelayNanos) )
108182 }
109183
184+ /// Calculates the maximum allowed delay in nanoseconds.
185+ ///
186+ /// - Parameter maxInterval: An optional maximum delay value.
187+ /// - Returns: The maximum delay in nanoseconds, clamped to `UInt64.max`.
110188 private func calculateMaxDelay( _ maxInterval: DispatchTimeInterval ? ) -> Double {
111189 guard let maxSeconds = maxInterval? . double else {
112190 return Double ( UInt64 . max)
@@ -116,6 +194,16 @@ struct RetryIterator: IteratorProtocol {
116194 return min ( maxNanos, Double ( UInt64 . max) )
117195 }
118196
197+ /// Applies random jitter to a delay value.
198+ ///
199+ /// Jitter helps prevent synchronized retries (the "thundering herd" problem)
200+ /// by randomizing retry timings within a defined range.
201+ ///
202+ /// - Parameters:
203+ /// - value: The base delay value in nanoseconds.
204+ /// - factor: The jitter factor defining the randomization range.
205+ /// - maxDelay: The maximum allowed delay.
206+ /// - Returns: A jittered delay value clamped to valid bounds.
119207 private func applyJitter(
120208 to value: Double ,
121209 factor: Double ,
0 commit comments