Skip to content

Alamofire

RequestAdapter

Alamofire's RequestAdapter protocol allows each URLRequest to be inspected and mutated before being issued over the network.

RequestRetrier

Alamofire's RequestRetrier protocol allows a Request that encountered an Error while being executed to be retried.

swift
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)

RequestInterceptors

Alamofire supports the use of multiple RequestInterceptors at both the Session and Request levels through the use of the Interceptor type.

When composed of multiple RequestAdapters, Interceptor will call each RequestAdapter in succession. If they all succeed, the final URLRequest out of the chain of RequestAdapters will be used to perform the request. If one fails, adaptation stops and the Request fails with the error returned.

swift
private func adapt(_ urlRequest: URLRequest,
                   for session: Session,
                   using adapters: [any RequestAdapter],
                   completion: @escaping @Sendable (Result<URLRequest, any Error>) -> Void) {
    var pendingAdapters = adapters
    guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return }
    let adapter = pendingAdapters.removeFirst()
    adapter.adapt(urlRequest, for: session) { [pendingAdapters] result in
        switch result {
        case let .success(urlRequest):
            self.adapt(urlRequest, for: session, using: pendingAdapters, completion: completion)
        case .failure:
            completion(result)
        }
    }
}
private func adapt(_ urlRequest: URLRequest,
                   for session: Session,
                   using adapters: [any RequestAdapter],
                   completion: @escaping @Sendable (Result<URLRequest, any Error>) -> Void) {
    var pendingAdapters = adapters
    guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return }
    let adapter = pendingAdapters.removeFirst()
    adapter.adapt(urlRequest, for: session) { [pendingAdapters] result in
        switch result {
        case let .success(urlRequest):
            self.adapt(urlRequest, for: session, using: pendingAdapters, completion: completion)
        case .failure:
            completion(result)
        }
    }
}

Similarly, when composed of multiple RequestRetriers, retries are executed in the same order as the retriers were added to the instance, until either all of them complete or one of them fails with an error.

swift
private func retry(_ request: Request,
                   for session: Session,
                   dueTo error: any Error,
                   using retriers: [any RequestRetrier],
                   completion: @escaping @Sendable (RetryResult) -> Void) {
    var pendingRetriers = retriers
    guard !pendingRetriers.isEmpty else { completion(.doNotRetry); return }
    let retrier = pendingRetriers.removeFirst()
    retrier.retry(request, for: session, dueTo: error) { [pendingRetriers] result in
        switch result {
        case .retry, .retryWithDelay, .doNotRetryWithError:
            completion(result)
        case .doNotRetry:
            // Only continue to the next retrier if retry was not triggered and no error was encountered
            self.retry(request, for: session, dueTo: error, using: pendingRetriers, completion: completion)
        }
    }
}
private func retry(_ request: Request,
                   for session: Session,
                   dueTo error: any Error,
                   using retriers: [any RequestRetrier],
                   completion: @escaping @Sendable (RetryResult) -> Void) {
    var pendingRetriers = retriers
    guard !pendingRetriers.isEmpty else { completion(.doNotRetry); return }
    let retrier = pendingRetriers.removeFirst()
    retrier.retry(request, for: session, dueTo: error) { [pendingRetriers] result in
        switch result {
        case .retry, .retryWithDelay, .doNotRetryWithError:
            completion(result)
        case .doNotRetry:
            // Only continue to the next retrier if retry was not triggered and no error was encountered
            self.retry(request, for: session, dueTo: error, using: pendingRetriers, completion: completion)
        }
    }
}

References