Last active
January 18, 2025 22:50
-
-
Save Aayush9029/d7ad6cadb3f1ef8c4310b2e19e164aa4 to your computer and use it in GitHub Desktop.
Network Retry Client
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Dependencies | |
| import Foundation | |
| import os | |
| import XCTestDynamicOverlay | |
| // Add Logger | |
| private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "RetryClient", category: "Retry") | |
| public struct RetryClient { | |
| public var retry: @Sendable (RetryConfiguration, @Sendable () async throws -> Void) async -> Void | |
| // Consolidated retry logic (non-throwing version) | |
| private static func retryOperation<T>( | |
| configuration: RetryConfiguration, | |
| task: @Sendable () async throws -> T | |
| ) async -> T? { | |
| var lastError: Error? | |
| var currentBackoff = configuration.backoffDuration | |
| logger.info("Starting retry task with configuration: maxAttempts=\(configuration.maxAttempts), initialBackoff=\(configuration.backoffDuration)s") | |
| for attempt in 1 ... configuration.maxAttempts { | |
| do { | |
| logger.debug("Attempting execution (attempt \(attempt)/\(configuration.maxAttempts))") | |
| let result = try await task() | |
| logger.info("Task succeeded on attempt \(attempt)") | |
| return result | |
| } catch { | |
| lastError = error | |
| logger.error("Attempt \(attempt) failed: \(error.localizedDescription)") | |
| if attempt < configuration.maxAttempts { | |
| logger.debug("Waiting \(String(format: "%.2f", currentBackoff))s before next attempt") | |
| try? await Task.sleep(for: .seconds(currentBackoff)) | |
| currentBackoff *= configuration.backoffMultiplier | |
| } | |
| } | |
| } | |
| // Log the final error if we couldn't succeed after all retries | |
| if let error = lastError { | |
| logger.error("Final error after \(configuration.maxAttempts) attempts: \(error.localizedDescription)") | |
| } | |
| return nil | |
| } | |
| // Public method for retry task | |
| public func retryTask<T>( | |
| configuration: RetryConfiguration = .default, | |
| task: @Sendable () async throws -> T | |
| ) async throws -> T { | |
| if let result = await RetryClient.retryOperation(configuration: configuration, task: task) { | |
| return result | |
| } else { | |
| throw RetryError.maxAttemptsExceeded | |
| } | |
| } | |
| } | |
| // MARK: - Dependency Registration | |
| extension RetryClient: DependencyKey { | |
| public static let liveValue = Self( | |
| retry: { configuration, operation in | |
| // Use the static retryOperation method since it's now accessible without needing 'self' | |
| await retryOperation(configuration: configuration) { | |
| try await operation() | |
| } | |
| } | |
| ) | |
| } | |
| public extension DependencyValues { | |
| var retryClient: RetryClient { | |
| get { self[RetryClient.self] } | |
| set { self[RetryClient.self] = newValue } | |
| } | |
| } | |
| enum RetryError: Error { | |
| case maxAttemptsExceeded | |
| } | |
| public struct RetryConfiguration { | |
| let maxAttempts: Int | |
| /// - backoffDuration: second initial delay between retries | |
| let backoffDuration: TimeInterval | |
| /// - backoffMultiplier: increase in delay for each subsequent retry | |
| let backoffMultiplier: Double | |
| public static let `default` = RetryConfiguration( | |
| maxAttempts: 3, | |
| backoffDuration: 5.0, | |
| backoffMultiplier: 3.0 | |
| ) | |
| public static let aggressive = RetryConfiguration( | |
| maxAttempts: 5, | |
| backoffDuration: 2.0, | |
| backoffMultiplier: 1.5 | |
| ) | |
| public static let conservative = RetryConfiguration( | |
| maxAttempts: 3, | |
| backoffDuration: 10.0, | |
| backoffMultiplier: 5.0 | |
| ) | |
| public init( | |
| maxAttempts: Int, | |
| backoffDuration: TimeInterval, | |
| backoffMultiplier: Double | |
| ) { | |
| self.maxAttempts = maxAttempts | |
| self.backoffDuration = backoffDuration | |
| self.backoffMultiplier = backoffMultiplier | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.