Previous navigation:

Alamofire source learning directory collection

Brief introduction:

RequestInterceptor a RequestInterceptor is a protocol that intercepts a request in the request flow and does the necessary processing on the request. It is a combination protocol: the RequestAdapter RequestAdapter and the RequestRetrier request reprocessor. Users can implement their own request interceptors, adapt urlRequests to their needs, or define their own retry logic. They can also use the simple adapter that Alamofire implements by default, and set the composite protocol. RequestRetrier is the interception preprocessing of response retry, which just happens to be a pair of requests and responses, and is combined as a unified object for incoming use when the Request is generated.

RequestAdapter RequestAdapter:

There is only one method, which ADAPTS the URLRequest after it has created the initial URLRequest, and notifies the listener before and after the adaptation.

/// Take the initial URLRequest, the Session, and the callback after the adaptation. The callback parameter is the Result object, which can be the URLRequest object after the successful adaptation, or it can return an error, which will be thrown upward from the creation of requestAdaptationFailed error
func adapt(_ urlRequest: URLRequest.for session: Session.completion: @escaping (Result<URLRequest.Error- > >)Void)
Copy the code

RequestRetrier Requests the reticulator

There is also only one method to decide whether to throw an error or try again according to some logic if the request fails:

    // The parameters are: Request object, Session, error message of Request failure, and retry logic callback. The callback parameter is retry logic, and the caller determines the retry behavior according to the logic
    func retry(_ request: Request.for session: Session.dueTo error: Error.completion: @escaping (RetryResult) - >Void)
Copy the code

RetryResult Retry logic

Four retry logics are defined

public enum RetryResult {
    /// Retry immediately
    case retry
    /// Delay retry
    case retryWithDelay(TimeInterval)
    // Complete the request without retry
    case doNotRetry
    /// Do not retry and throw an error
    case doNotRetryWithError(Error)}// Expand to get the information quickly, with two optional value attributes for quick judgment
extension RetryResult {
    /// Whether to retry
    var retryRequired: Bool {
        switch self {
        case .retry, .retryWithDelay: return true
        default: return false}}/// Delay the retry time
    var delay: TimeInterval? {
        switch self {
        case let .retryWithDelay(delay): return delay
        default: return nil}}/// Do not retry and throw the error message
    var error: Error? {
        guard case let .doNotRetryWithError(error) = self else { return nil }
        return error
    }
}
Copy the code

The combination generates a RequestInterceptor

Combine two protocols directly for external implementation

public protocol RequestInterceptor: RequestAdapter.RequestRetrier {}
// This is an extension that allows you to not implement the method even if you follow the protocol and still not report an error
extension RequestInterceptor {
    public func adapt(_ urlRequest: URLRequest.for session: Session.completion: @escaping (Result<URLRequest.Error- > >)Void) {
        // Return the original request
        completion(.success(urlRequest))
    }

    public func retry(_ request: Request.for session: Session.dueTo error: Error.completion: @escaping (RetryResult) - >Void) {
        / / not retry
        completion(.doNotRetry)
    }
}
Copy the code

Alamofire’s pre-defined request interceptors:

Alamofire has a number of pre-defined and simple interceptors that can be used directly or implemented according to your needs

1. Adapter: Simple request Adapter based on closures:

// We define a closure to accommodate the request:
public typealias AdaptHandler = (URLRequest.Session._ completion: @escaping (Result<URLRequest.Error- > >)Void) - >Void
// Then implement a closure based request adapter that simply holds a closure to accommodate the request:
open class Adapter: RequestInterceptor {
    private let adaptHandler: AdaptHandler

    public init(_ adaptHandler: @escaping AdaptHandler) {
        self.adaptHandler = adaptHandler
    }

    open func adapt(_ urlRequest: URLRequest.for session: Session.completion: @escaping (Result<URLRequest.Error- > >)Void) {
        adaptHandler(urlRequest, session, completion)
    }
}
Copy the code

Retrier: Simple rereater based on closure:

// We define a closure to determine the retry logic:
public typealias RetryHandler = (Request.Session.Error._ completion: @escaping (RetryResult) - >Void) - >Void
// Then implement a closure based rereiter:
open class Retrier: RequestInterceptor {
    private let retryHandler: RetryHandler

    public init(_ retryHandler: @escaping RetryHandler) {
        self.retryHandler = retryHandler
    }

    open func retry(_ request: Request.for session: Session.dueTo error: Error.completion: @escaping (RetryResult) - >Void) {
        retryHandler(request, session, error, completion)
    }
}
Copy the code

3. Interceptor: You can hold multiple adapters and retries

Hold two arrays, one for adapter objects and one for retries, and process them one by one when retrying, so pay attention to the order of incoming

open class Interceptor: RequestInterceptor {
    /// Save the adapter, any error will be thrown
    public let adapters: [RequestAdapter]
    /// All `RequestRetrier`s associated with the instance. These retriers will be run one at a time until one triggers retry.
    /// Save the retries. If any of the retries appear (immediately retry or delayed retry), the retries will be stopped and thrown. Any one that does not retry and throw an error will also stop and throw an error.
    public let retriers: [RequestRetrier]
    /// You can also create a single combinator using the rereater and adapter callbacks
    public init(adaptHandler: @escaping AdaptHandler.retryHandler: @escaping RetryHandler) {
        adapters = [Adapter(adaptHandler)]
        retriers = [Retrier(retryHandler)]
    }
    /// initialize with two arrays
    public init(adapter: RequestAdapter.retrier: RequestRetrier) {
        adapters = [adapter]
        retriers = [retrier]
    }
    /// Initializes with an adapter + rereiter + interceptor array, which adds the interceptor array to both the adapter and the reiter array
    public init(adapters: [RequestAdapter] =[].retriers: [RequestRetrier] =[].interceptors: [RequestInterceptor] = []) {
        self.adapters = adapters + interceptors
        self.retriers = retriers + interceptors
    }
    /// The adapter proxy method, call the following private method
    open func adapt(_ urlRequest: URLRequest.for session: Session.completion: @escaping (Result<URLRequest.Error- > >)Void) {
        adapt(urlRequest, for: session, using: adapters, completion: completion)
    }
    /// private adaptation method, will recurse
    private func adapt(_ urlRequest: URLRequest.for session: Session.using adapters: [RequestAdapter].completion: @escaping (Result<URLRequest.Error- > >)Void) {
        // To prepare the recursion array
        var pendingAdapters = adapters
        // If the recursion is empty, perform the callback and return
        guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return }
        // Take out the first adapter
        let adapter = pendingAdapters.removeFirst()
        / / you
        adapter.adapt(urlRequest, for: session) { result in
            switch result {
            case let .success(urlRequest):
                // The adaptation passes, and recursively ADAPTS the rest
                self.adapt(urlRequest, for: session, using: pendingAdapters, completion: completion)
            case .failure:
                // If the adaptation fails, an error is thrown
                completion(result)
            }
        }
    }
    // Rereiter logic, adjust the following private method
    open func retry(_ request: Request.for session: Session.dueTo error: Error.completion: @escaping (RetryResult) - >Void) {
        retry(request, for: session, dueTo: error, using: retriers, completion: completion)
    }
    // Private retry logic, which is called recursively
    private func retry(_ request: Request.for session: Session.dueTo error: Error.using retriers: [RequestRetrier].completion: @escaping (RetryResult) - >Void) {
        // An array of rereters for recursion
        var pendingRetriers = retriers
        // Return no retry if the recursion has completed and no retry or error has been triggered
        guard !pendingRetriers.isEmpty else { completion(.doNotRetry); return }
        // Take the first one
        let retrier = pendingRetriers.removeFirst()
        / / you
        retrier.retry(request, for: session, dueTo: error) { result in
            switch result {
            case .retry, .retryWithDelay, .doNotRetryWithError:
                // Retry, delay retry, do not retry and throw an error, execute callback
                completion(result)
            case .doNotRetry:
                // Otherwise recursion starts
                self.retry(request, for: session, dueTo: error, using: pendingRetriers, completion: completion)
            }
        }
    }
}
Copy the code

The above is purely personal understanding, it is inevitable to make mistakes, if you find any mistakes, please comment and point out ~~ will correct as soon as possible, also welcome to comment and discuss, thank you very much ~~