| | |
| | | |
| | | /// `Request` is the common superclass of all Alamofire request types and provides common state, delegate, and callback |
| | | /// handling. |
| | | public class Request { |
| | | public class Request: @unchecked Sendable { |
| | | /// State of the `Request`, with managed transitions between states set when calling `resume()`, `suspend()`, or |
| | | /// `cancel()` on the `Request`. |
| | | public enum State { |
| | |
| | | func canTransitionTo(_ state: State) -> Bool { |
| | | switch (self, state) { |
| | | case (.initialized, _): |
| | | return true |
| | | true |
| | | case (_, .initialized), (.cancelled, _), (.finished, _): |
| | | return false |
| | | false |
| | | case (.resumed, .cancelled), (.suspended, .cancelled), (.resumed, .suspended), (.suspended, .resumed): |
| | | return true |
| | | true |
| | | case (.suspended, .suspended), (.resumed, .resumed): |
| | | return false |
| | | false |
| | | case (_, .finished): |
| | | return true |
| | | true |
| | | } |
| | | } |
| | | } |
| | |
| | | /// The queue used for all serialization actions. By default it's a serial queue that targets `underlyingQueue`. |
| | | public let serializationQueue: DispatchQueue |
| | | /// `EventMonitor` used for event callbacks. |
| | | public let eventMonitor: EventMonitor? |
| | | public let eventMonitor: (any EventMonitor)? |
| | | /// The `Request`'s interceptor. |
| | | public let interceptor: RequestInterceptor? |
| | | public let interceptor: (any RequestInterceptor)? |
| | | /// The `Request`'s delegate. |
| | | public private(set) weak var delegate: RequestDelegate? |
| | | public private(set) weak var delegate: (any RequestDelegate)? |
| | | |
| | | // MARK: - Mutable State |
| | | |
| | |
| | | /// `ProgressHandler` and `DispatchQueue` provided for download progress callbacks. |
| | | var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? |
| | | /// `RedirectHandler` provided for to handle request redirection. |
| | | var redirectHandler: RedirectHandler? |
| | | var redirectHandler: (any RedirectHandler)? |
| | | /// `CachedResponseHandler` provided to handle response caching. |
| | | var cachedResponseHandler: CachedResponseHandler? |
| | | var cachedResponseHandler: (any CachedResponseHandler)? |
| | | /// Queue and closure called when the `Request` is able to create a cURL description of itself. |
| | | var cURLHandler: (queue: DispatchQueue, handler: (String) -> Void)? |
| | | var cURLHandler: (queue: DispatchQueue, handler: @Sendable (String) -> Void)? |
| | | /// Queue and closure called when the `Request` creates a `URLRequest`. |
| | | var urlRequestHandler: (queue: DispatchQueue, handler: (URLRequest) -> Void)? |
| | | var urlRequestHandler: (queue: DispatchQueue, handler: @Sendable (URLRequest) -> Void)? |
| | | /// Queue and closure called when the `Request` creates a `URLSessionTask`. |
| | | var urlSessionTaskHandler: (queue: DispatchQueue, handler: (URLSessionTask) -> Void)? |
| | | var urlSessionTaskHandler: (queue: DispatchQueue, handler: @Sendable (URLSessionTask) -> Void)? |
| | | /// Response serialization closures that handle response parsing. |
| | | var responseSerializers: [() -> Void] = [] |
| | | var responseSerializers: [@Sendable () -> Void] = [] |
| | | /// Response serialization completion closures executed once all response serializers are complete. |
| | | var responseSerializerCompletions: [() -> Void] = [] |
| | | var responseSerializerCompletions: [@Sendable () -> Void] = [] |
| | | /// Whether response serializer processing is finished. |
| | | var responseSerializerProcessingFinished = false |
| | | /// `URLCredential` used for authentication challenges. |
| | |
| | | // MARK: Progress |
| | | |
| | | /// Closure type executed when monitoring the upload or download progress of a request. |
| | | public typealias ProgressHandler = (Progress) -> Void |
| | | public typealias ProgressHandler = @Sendable (_ progress: Progress) -> Void |
| | | |
| | | /// `Progress` of the upload of the body of the executed `URLRequest`. Reset to `0` if the `Request` is retried. |
| | | public let uploadProgress = Progress(totalUnitCount: 0) |
| | |
| | | // MARK: Redirect Handling |
| | | |
| | | /// `RedirectHandler` set on the instance. |
| | | public internal(set) var redirectHandler: RedirectHandler? { |
| | | public internal(set) var redirectHandler: (any RedirectHandler)? { |
| | | get { mutableState.redirectHandler } |
| | | set { mutableState.redirectHandler = newValue } |
| | | } |
| | |
| | | // MARK: Cached Response Handling |
| | | |
| | | /// `CachedResponseHandler` set on the instance. |
| | | public internal(set) var cachedResponseHandler: CachedResponseHandler? { |
| | | public internal(set) var cachedResponseHandler: (any CachedResponseHandler)? { |
| | | get { mutableState.cachedResponseHandler } |
| | | set { mutableState.cachedResponseHandler = newValue } |
| | | } |
| | |
| | | // MARK: Validators |
| | | |
| | | /// `Validator` callback closures that store the validation calls enqueued. |
| | | let validators = Protected<[() -> Void]>([]) |
| | | let validators = Protected<[@Sendable () -> Void]>([]) |
| | | |
| | | // MARK: URLRequests |
| | | |
| | |
| | | init(id: UUID = UUID(), |
| | | underlyingQueue: DispatchQueue, |
| | | serializationQueue: DispatchQueue, |
| | | eventMonitor: EventMonitor?, |
| | | interceptor: RequestInterceptor?, |
| | | delegate: RequestDelegate) { |
| | | eventMonitor: (any EventMonitor)?, |
| | | interceptor: (any RequestInterceptor)?, |
| | | delegate: any RequestDelegate) { |
| | | self.id = id |
| | | self.underlyingQueue = underlyingQueue |
| | | self.serializationQueue = serializationQueue |
| | |
| | | dispatchPrecondition(condition: .onQueue(underlyingQueue)) |
| | | |
| | | mutableState.read { state in |
| | | state.urlRequestHandler?.queue.async { state.urlRequestHandler?.handler(request) } |
| | | guard let urlRequestHandler = state.urlRequestHandler else { return } |
| | | |
| | | urlRequestHandler.queue.async { urlRequestHandler.handler(request) } |
| | | } |
| | | |
| | | eventMonitor?.request(self, didCreateURLRequest: request) |
| | |
| | | /// - Note: This method will also `resume` the instance if `delegate.startImmediately` returns `true`. |
| | | /// |
| | | /// - Parameter closure: The closure containing the response serialization call. |
| | | func appendResponseSerializer(_ closure: @escaping () -> Void) { |
| | | func appendResponseSerializer(_ closure: @escaping @Sendable () -> Void) { |
| | | mutableState.write { mutableState in |
| | | mutableState.responseSerializers.append(closure) |
| | | |
| | |
| | | /// Returns the next response serializer closure to execute if there's one left. |
| | | /// |
| | | /// - Returns: The next response serialization closure, if there is one. |
| | | func nextResponseSerializer() -> (() -> Void)? { |
| | | var responseSerializer: (() -> Void)? |
| | | func nextResponseSerializer() -> (@Sendable () -> Void)? { |
| | | var responseSerializer: (@Sendable () -> Void)? |
| | | |
| | | mutableState.write { mutableState in |
| | | let responseSerializerIndex = mutableState.responseSerializerCompletions.count |
| | |
| | | func processNextResponseSerializer() { |
| | | guard let responseSerializer = nextResponseSerializer() else { |
| | | // Execute all response serializer completions and clear them |
| | | var completions: [() -> Void] = [] |
| | | var completions: [@Sendable () -> Void] = [] |
| | | |
| | | mutableState.write { mutableState in |
| | | completions = mutableState.responseSerializerCompletions |
| | |
| | | /// |
| | | /// - Parameter completion: The completion handler provided with the response serializer, called when all serializers |
| | | /// are complete. |
| | | func responseSerializerDidComplete(completion: @escaping () -> Void) { |
| | | func responseSerializerDidComplete(completion: @escaping @Sendable () -> Void) { |
| | | mutableState.write { $0.responseSerializerCompletions.append(completion) } |
| | | processNextResponseSerializer() |
| | | } |
| | |
| | | /// - closure: The closure to be executed periodically as data is read from the server. |
| | | /// |
| | | /// - Returns: The instance. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func downloadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self { |
| | | mutableState.downloadProgressHandler = (handler: closure, queue: queue) |
| | |
| | | /// - closure: The closure to be executed periodically as data is sent to the server. |
| | | /// |
| | | /// - Returns: The instance. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func uploadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self { |
| | | mutableState.uploadProgressHandler = (handler: closure, queue: queue) |
| | |
| | | /// - Parameter handler: The `RedirectHandler`. |
| | | /// |
| | | /// - Returns: The instance. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func redirect(using handler: RedirectHandler) -> Self { |
| | | public func redirect(using handler: any RedirectHandler) -> Self { |
| | | mutableState.write { mutableState in |
| | | precondition(mutableState.redirectHandler == nil, "Redirect handler has already been set.") |
| | | mutableState.redirectHandler = handler |
| | |
| | | /// - Parameter handler: The `CachedResponseHandler`. |
| | | /// |
| | | /// - Returns: The instance. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func cacheResponse(using handler: CachedResponseHandler) -> Self { |
| | | public func cacheResponse(using handler: any CachedResponseHandler) -> Self { |
| | | mutableState.write { mutableState in |
| | | precondition(mutableState.cachedResponseHandler == nil, "Cached response handler has already been set.") |
| | | mutableState.cachedResponseHandler = handler |
| | |
| | | /// - handler: Closure to be called when the cURL description is available. |
| | | /// |
| | | /// - Returns: The instance. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func cURLDescription(on queue: DispatchQueue, calling handler: @escaping (String) -> Void) -> Self { |
| | | public func cURLDescription(on queue: DispatchQueue, calling handler: @escaping @Sendable (String) -> Void) -> Self { |
| | | mutableState.write { mutableState in |
| | | if mutableState.requests.last != nil { |
| | | queue.async { handler(self.cURLDescription()) } |
| | |
| | | /// `underlyingQueue` by default. |
| | | /// |
| | | /// - Returns: The instance. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func cURLDescription(calling handler: @escaping (String) -> Void) -> Self { |
| | | public func cURLDescription(calling handler: @escaping @Sendable (String) -> Void) -> Self { |
| | | cURLDescription(on: underlyingQueue, calling: handler) |
| | | |
| | | return self |
| | |
| | | /// - handler: Closure to be called when a `URLRequest` is available. |
| | | /// |
| | | /// - Returns: The instance. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func onURLRequestCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLRequest) -> Void) -> Self { |
| | | public func onURLRequestCreation(on queue: DispatchQueue = .main, perform handler: @escaping @Sendable (URLRequest) -> Void) -> Self { |
| | | mutableState.write { state in |
| | | if let request = state.requests.last { |
| | | queue.async { handler(request) } |
| | |
| | | /// - handler: Closure to be called when the `URLSessionTask` is available. |
| | | /// |
| | | /// - Returns: The instance. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func onURLSessionTaskCreation(on queue: DispatchQueue = .main, perform handler: @escaping (URLSessionTask) -> Void) -> Self { |
| | | public func onURLSessionTaskCreation(on queue: DispatchQueue = .main, perform handler: @escaping @Sendable (URLSessionTask) -> Void) -> Self { |
| | | mutableState.write { state in |
| | | if let task = state.tasks.last { |
| | | queue.async { handler(task) } |
| | |
| | | |
| | | extension Request { |
| | | /// Type indicating how a `DataRequest` or `DataStreamRequest` should proceed after receiving an `HTTPURLResponse`. |
| | | public enum ResponseDisposition { |
| | | public enum ResponseDisposition: Sendable { |
| | | /// Allow the request to continue normally. |
| | | case allow |
| | | /// Cancel the request, similar to calling `cancel()`. |
| | |
| | | |
| | | var sessionDisposition: URLSession.ResponseDisposition { |
| | | switch self { |
| | | case .allow: return .allow |
| | | case .cancel: return .cancel |
| | | case .allow: .allow |
| | | case .cancel: .cancel |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | /// Protocol abstraction for `Request`'s communication back to the `SessionDelegate`. |
| | | public protocol RequestDelegate: AnyObject { |
| | | public protocol RequestDelegate: AnyObject, Sendable { |
| | | /// `URLSessionConfiguration` used to create the underlying `URLSessionTask`s. |
| | | var sessionConfiguration: URLSessionConfiguration { get } |
| | | |
| | |
| | | /// - request: `Request` which failed. |
| | | /// - error: `Error` which produced the failure. |
| | | /// - completion: Closure taking the `RetryResult` for evaluation. |
| | | func retryResult(for request: Request, dueTo error: AFError, completion: @escaping (RetryResult) -> Void) |
| | | func retryResult(for request: Request, dueTo error: AFError, completion: @escaping @Sendable (RetryResult) -> Void) |
| | | |
| | | /// Asynchronously retry the `Request`. |
| | | /// |