Previous navigation:

Alamofire source learning directory collection

A brief introduction to the basic file alamofire. swift:

/// Reference to `Session.default` for quick bootstrapping and examples.
public let AF = Session.default

/// Current Alamofire version. Necessary since SPM doesn't use dynamic libraries. Plus this will be more accurate.
let version = "5.4.0"
Copy the code

One is a singleton of the Session class, and the other marks the current version number.

The Session class is the core class of ALamofire. It encapsulates the URLSession class and manages all request scheduling.

1. Related attributes:

    /// a default singleton
    public static let `default` = Session(a)/// Hold a URLSession to create the requested Task. Do not interact with the URLSessionTask held by this object. Otherwise, the internal logic of Alamofire will be affected
    public let session: URLSession
    
    /// Handles the URLSession delegate, URLSessionTaskDelegate, and request interception logic
    public let delegate: SessionDelegate
    
    // internal callback execution and status update queue must be serial queue
    public let rootQueue: DispatchQueue
    
    /// Whether to send the Request immediately when it is created. This property manages the startImmediately parameter when the Request is created. The default value is true
    public let startRequestsImmediately: Bool
    
    // create Request queue asynchronously. Default is rootQueue
    public let requestQueue: DispatchQueue
    
    /// Parse the response queue, which defaults to rootQueue
    public let serializationQueue: DispatchQueue
    
    /// Request interceptor interface, is a combination of RequestAdapter and RequestRetrier, default nil
    public let interceptor: RequestInterceptor?
    
    /// The certificate truster interface defaults to nil
    public let serverTrustManager: ServerTrustManager?
    
    /// redirects the handler interface to nil by default
    public let redirectHandler: RedirectHandler?
    
    /// Cache management interface, default nil
    public let cachedResponseHandler: CachedResponseHandler?
    
    /// The event monitor manager class, which handles events at all stages of the request life cycle, is initialized by default with defaultEventMonitors below and the incoming event detectors
    public let eventMonitor: CompositeEventMonitor
    
    // the default list of event monitor interfaces, with only one notification event monitor
    public let defaultEventMonitors: [EventMonitor] = [AlamofireNotifications()]

    /// Struct, which holds the mapping between Request and URLSessiontask, provides various methods to access task and Request, and determines the number of tasks
    var requestTaskMap = RequestTaskMap(a)/// The collection of requests currently being requested
    var activeRequests: Set<Request> = []
    
    /// Wait for a successful callback
    var waitingCompletions: [URLSessionTask: () - >Void] = [:]
Copy the code

2. Initialization

Session initialization has two methods, a necessary method and a convenient method.

    public init(session: URLSession.delegate: SessionDelegate.rootQueue: DispatchQueue.startRequestsImmediately: Bool = true.requestQueue: DispatchQueue? = nil.serializationQueue: DispatchQueue? = nil.interceptor: RequestInterceptor? = nil.serverTrustManager: ServerTrustManager? = nil.redirectHandler: RedirectHandler? = nil.cachedResponseHandler: CachedResponseHandler? = nil.eventMonitors: [EventMonitor] = []) {
        // Alamofire does not support background downloads. See the support in the back)
        precondition(session.configuration.identifier = = nil."Alamofire does not support background URLSessionConfigurations.")
        // URLSession Queue and rootQueue must be the same
        precondition(session.delegateQueue.underlyingQueue = = = rootQueue,
                     "Session(session:) initializer must be passed the DispatchQueue used as the delegateQueue's underlyingQueue as rootQueue.")

        self.session = session
        self.delegate = delegate
        self.rootQueue = rootQueue
        self.startRequestsImmediately = startRequestsImmediately
        RootQueue is the default target to create a request/response resolution queue
        self.requestQueue = requestQueue ?? DispatchQueue(label: "\(rootQueue.label).requestQueue", target: rootQueue)
        self.serializationQueue = serializationQueue ?? DispatchQueue(label: "\(rootQueue.label).serializationQueue", target: rootQueue)
        // Four default nil properties
        self.interceptor = interceptor
        self.serverTrustManager = serverTrustManager
        self.redirectHandler = redirectHandler
        self.cachedResponseHandler = cachedResponseHandler
        // Initialize the CompositeEventMonitor object based on the incoming event listeners and the default listeners
        eventMonitor = CompositeEventMonitor(monitors: defaultEventMonitors + eventMonitors)
        // Throw the composite listener object to the SessionDelegate object to manage the task
        delegate.eventMonitor = eventMonitor
        // Handling Session changes on request
        delegate.stateProvider = self
    }
Copy the code

The convenient initialization method uses the default URLSessionConfiguration and request queue to initialize the URLSession, SessionDelegate, and rootQueue

3, deinit processing:

    deinit {
        finishRequestsForDeinit()
        session.invalidateAndCancel()
    }
    func finishRequestsForDeinit(a) {
        // A sessionDeinitialized error is returned after each Request has not completed the notification
        requestTaskMap.requests.forEach { request in
            rootQueue.async {
                request.finish(error: AFError.sessionDeinitialized)
            }
        }
    }
Copy the code

Iv. Processing of all requests:

    // Execute a closure on all requests in progress
    public func withAllRequests(perform action: @escaping (Set<Request- > >)Void) {
        rootQueue.async {
            action(self.activeRequests)
        }
    }
    // Cancel all requests
    public func cancelAllRequests(completingOnQueue queue: DispatchQueue = .main, completion: (() - >Void)? = nil) {
        withAllRequests { requests in
            requests.forEach { $0.cancel() }
            queue.async {
                completion?()}}}Copy the code

Request initialization:

To define the first RequestConvertible URLRequestConvertible agreement with RequestEncodableConvertible structure, used to create URLRequestConvertible object using different parameters

    // Transform the closure of the URLRequest object
    public typealias RequestModifier = (inout URLRequest) throws -> Void
    // A common request converter that uses the ParameterEncoding protocol object to encode parameters
    struct RequestConvertible: URLRequestConvertible {
        let url: URLConvertible//url
        let method: HTTPMethod/ / method
        let parameters: Parameters?/ / parameters
        let encoding: ParameterEncoding// Parameter encoding object, default URL encoding
        let headers: HTTPHeaders?/ / request header
        let requestModifier: RequestModifier?
    }
    // For converters whose parameters are Encodable, use the ParameterEncoder protocol object to encode parameters
    struct RequestEncodableConvertible<Parameters: Encodable> :URLRequestConvertible {
        let url: URLConvertible
        let method: HTTPMethod
        let parameters: Parameters?
        let encoder: ParameterEncoder
        let headers: HTTPHeaders?
        let requestModifier: RequestModifier?
    }
Copy the code

For common request, streamRequest, and downloadRequest, there are three ways to create a request:

  1. Use the parameter to create a RequestConvertible object, then convert to URLRequestConvertible and use 3 to create the corresponding Request object
  2. Create using RequestConvertible, same logic as 1
  3. Create Request object using URLRequestConvertible object +rootQueue+serializationQueue+eventMonitor Specify the RequestDelegate as self to handle the URLSessionConfiguration, cleanup, and retry logic
  • DownloadRequest has an additional breakpoint continuation method that uses the downloaded Data to initialize the downloadRequest object

For UploadRequest, and defines the ParameterlessRequestConvertible structure to realize URLRequestConvertible agreement, characterized by no parameters. Then define the Upload structure realizes the UploadConvertible used to encapsulate the request and uploadable for UploadRequest are used to create ParameterlessRequestConvertible object, There are a total of X ways to create UploadRequest:

  1. Use the data + based parameter request, Mr Into ParameterlessRequestConvertible object, 2
  2. Using the data, URLRequestConvertible object, generate the Uploadable. Data object, go to 11
  3. Use Mr Into ParameterlessRequestConvertible fileURL + based parameter request, object, 4
  4. Use fileURL ParameterlessRequestConvertible object, generating Uploadable. The file object, 11
  5. Use InputStream + base parameter to go to 6
  6. Use the InputStream, ParameterlessRequestConvertible, generate Uploadable. The stream object, 11
  7. Based parameter, using multiple form closure + request execution closure generated MultipartFormData, ParameterlessRequestConvertible, convert URLRequestConvertible, turn 10
  8. Using multi-form closure +URLRequestConvertible, perform the closure, go to 10
  9. Using MultipartFormData object + basic request parameters, generate ParameterlessRequestConvertible, MultipartUpload, turned 12
  10. Use the MultipartFormData object +URLRequestConvertible to generate MultipartUpload, go to 12
  • The following is the Intenal API, which cannot be accessed externally
  1. Uploadable + URLRequestConvertible create Upload, go to 12 (not used in Session)
  2. The UploadRequest is generated and sent using the UploadConvertible protocol object

Note:
  1. The upload method is divided into three types: Data (memory), fileURL(disk), InputStream(disk), and 7-10 all process multiple forms of data with an encodingMemoryThreshold parameter, which determines the encoding memory limit. If the form data size is larger than this limit, The code will save the form data to disk and use iostream to process it to avoid excessive memory. The type of Uploadable generated depends on the form encoding type.
  2. All the upload the url of the query parameter, so use is ParameterlessRequestConvertible structure to transform the object URLRequestConvertible agreement.
  3. The 12 methods of upload all call each other, and the final data coding is in the corresponding Request module

Prepare to send the request

The main entrance is:

    // MARK: Perform

    /// Starts performing the provided `Request`.
    ///
    /// - Parameter request: The `Request` to perform.
    func perform(_ request: Request) {
        rootQueue.async {
            // Check the rootQueue to see if the request was canceled
            guard !request.isCancelled else { return }
            // Insert into the collection of requests being requested
            self.activeRequests.insert(request)
            // Send requests in the requestQueue
            self.requestQueue.async {
                // Subclasses must first case or they will be recognized as superclasses
                switch request {
                case let r as UploadRequest: 
                     UploadRequest is a subclass of DataRequest
                    self.performUploadRequest(r)
                    // Create Uploadable first
                    // Then notify the event listener in rootQueue didCreateUploadable, and then call performSetupOperations
                    / / create fails in rootQueue told a monitor didFailToCreateUploadable first, the error message for createUploadableFailed
                    // Then decide whether to retry in the request
                case let r as DataRequest: 
                    self.performDataRequest(r)
                    // Adjust performSetupOperations directly
                case let r as DownloadRequest: 
                    self.performDownloadRequest(r)
                    // Determine the downloadable of the request
                    // For new downloads, call performSetupOperations directly
                    // In the case of breakpoint continuation, call the didReceiveResumeData method on rootQueue. See the breakpoint continuation section below
                case let r as DataStreamRequest: 
                    self.performDataStreamRequest(r)
                    // Adjust performSetupOperations directly
                default: 
                    fatalError("Attempted to perform unsupported Request subclass: \ [type(of: request))")}}}}Copy the code

The performSetupOperations method takes two arguments: the Request object and the URLRequestConvertible protocol object, which is derived from the Request. Convertible property. After successful processing, the didCreateURLRequest method is called to update the status

    func performSetupOperations(for request: Request.convertible: URLRequestConvertible) {
        // Currently in the requestQueue
        dispatchPrecondition(condition: .onQueue(requestQueue))
        //URLRequestConvertible specifies the URLRequest generated
        let initialRequest: URLRequest

        do {
            initialRequest = try convertible.asURLRequest()
            // Check whether the request is valid (get requests cannot contain the body parameter)
            try initialRequest.validate()
        } catch {
            // If there is an error in the rootQueue, an error is reported
            rootQueue.async { request.didFailToCreateURLRequest(with: error.asAFError(or: .createURLRequestFailed(error: error))) }
            return
        }
        / / in rootQueue notification request, initialize the URLRequest success (using MutableState record state, told the event listener didCreateInitialURLRequest)
        rootQueue.async { request.didCreateInitialURLRequest(initialRequest) }
        
        guard !request.isCancelled else { return }
        // Check whether there is a request adapter, internal logic:
        //1. If the request and Session interceptors are not null, return the generated composite interceptor
        //2. Return a request or Session interceptor
        guard let adapter = adapter(for: request) else {
            // If there is no interceptor, notify directly
            rootQueue.async { self.didCreateURLRequest(initialRequest, for: request) }
            return
        }
        // Use the adapter in the interceptor to preprocess the request
        adapter.adapt(initialRequest, for: self) { result in
            do {
                let adaptedRequest = try result.get()
                try adaptedRequest.validate()
                // Notification processing is complete
                self.rootQueue.async {
                    request.didAdaptInitialRequest(initialRequest, to: adaptedRequest)
                    self.didCreateURLRequest(adaptedRequest, for: request)
                }
            } catch {
                // Any error will raise requestAdaptationFailed
                self.rootQueue.async { request.didFailToAdaptURLRequest(initialRequest, withError: .requestAdaptationFailed(error: error)) }
            }
        }
    }
Copy the code

Create an URLSessionTask and send the request

When the creation request is complete and the interceptor processing is complete, the logic will come in here. Except for the breakpoint continuation request, which executes the didReceiveResumeData method, all other requests will execute the didCreateURLRequest method. The updateStatesForTask method is then eventually called to update

    func didCreateURLRequest(_ urlRequest: URLRequest.for request: Request) {
        dispatchPrecondition(condition: .onQueue(rootQueue))
        // The request was created successfully
        request.didCreateURLRequest(urlRequest)

        guard !request.isCancelled else { return }
        // Create an URLSessionTask. The base class Request implements this method, and several subclasses implement it separately
        let task = request.task(for: urlRequest, using: session)
        // Write the Session request-task data pair and save it
        requestTaskMap[request] = task
        // After the creation of the URLSessionTask succeeds, the request performs related processing, the thread safely saves the task, and notifies the event listener that the creation of the task succeeds
        request.didCreateTask(task)

        updateStatesForTask(task, request: request)
    }
    // The basic logic is similar to the above, but the method of creating task is different, using the downloaded Data to create URLSessionDownloadTask
    func didReceiveResumeData(_ data: Data.for request: DownloadRequest) {
        dispatchPrecondition(condition: .onQueue(rootQueue))

        guard !request.isCancelled else { return }

        let task = request.task(forResumeData: data, using: session)
        requestTaskMap[request] = task
        request.didCreateTask(task)

        updateStatesForTask(task, request: request)
    }
    // When the above two methods are complete, this method is called to start sending requests
    func updateStatesForTask(_ task: URLSessionTask.request: Request) {
        // Confirm to update the state of URLSessionTask in the rootQueue
        dispatchPrecondition(condition: .onQueue(rootQueue))
        // Update the status of the task based on the request status
        request.withState { state in
            switch state {
            case .initialized, .finished:
                // Initialize or request complete, do nothing
                break
            case .resumed:
                // Send the request
                task.resume()
                // Tell the request task to start the request
                rootQueue.async { request.didResumeTask(task) }
            case .suspended:
                // The request is suspended
                task.suspend()
                rootQueue.async { request.didSuspendTask(task) }
            case .cancelled:
                // Restore the task and then cancel it
                task.resume()
                task.cancel()
                rootQueue.async { request.didCancelTask(task) }
            }
        }
    }
Copy the code

Then wait for the request’s respective RequestDelegate to handle the landing logic

8. Request adapters and retries:

    The Interceptor class is used to combine the Session default and the separate adapter/rereserver for each request
    func adapter(for request: Request) -> RequestAdapter? {
        if let requestInterceptor = request.interceptor, let sessionInterceptor = interceptor {
            return Interceptor(adapters: [requestInterceptor, sessionInterceptor])
        } else {
            return request.interceptor ?? interceptor
        }
    }

    func retrier(for request: Request) -> RequestRetrier? {
        if let requestInterceptor = request.interceptor, let sessionInterceptor = interceptor {
            return Interceptor(retriers: [requestInterceptor, sessionInterceptor])
        } else {
            return request.interceptor ?? interceptor
        }
    }
Copy the code

Nine, RequestDelegate

Each Request object has a RequestDelegate proxy object that gets the URLSessionConfiguration and global startImmediately when the Request is created, and handles error retry and delayed retry logic. It points to the Session that created the Request, so the RequestDelegate protocol is implemented in the Session

Ten, SessionStateProvider

The Session holds the SessionDelegate that handles the Task, and the SessionDelegate holds a RequestTaskMap that points to the Session, To handle state processing when Task state changes (because Session holds a RequestTaskMap to store the mapping between Request and NSURLSessionTask), Therefore, when the SessionDelegate responds to the proxy callback of the URLSessionDelegate and each NSURLSessionTask, it will call back to the SessionStateProvider protocol method in the Session. To obtain the corresponding Request according to the Task, and determine whether the Request is completed to disconnect the mapping.

// All operations are performed in rootQueue
extension Session: SessionStateProvider {
    func request(for task: URLSessionTask) -> Request? {
        dispatchPrecondition(condition: .onQueue(rootQueue))
        // Obtain the Request from task
        return requestTaskMap[task]
    }

    func didGatherMetricsForTask(_ task: URLSessionTask) {
        dispatchPrecondition(condition: .onQueue(rootQueue))
        //task Indicates the behavior after the task indicator is successfully obtained
        // In the RequestTaskMap structure, check whether the request needs to be disconnected from the task
        let didDisassociate = requestTaskMap.disassociateIfNecessaryAfterGatheringMetricsForTask(task)
        // The waitingCompletions closure comes from the follow-up operation set after the task request succeeds. If the task indicator is successfully obtained, the follow-up operation is performed until the request is complete
        if didDisassociate {
            waitingCompletions[task]?()
            waitingCompletions[task] = nil}}func didCompleteTask(_ task: URLSessionTask.completion: @escaping() - >Void) {
        dispatchPrecondition(condition: .onQueue(rootQueue))
        // Check whether the request needs to be disconnected from the task
        let didDisassociate = requestTaskMap.disassociateIfNecessaryAfterCompletingTask(task)
        // If there is no follow-up logic, execute the complete callback directly. Otherwise, save the callback in the waitingCompletions dictionary, and wait to check the task indicators before deciding whether to disconnect the link
        if didDisassociate {
            completion()
        } else {
            waitingCompletions[task] = completion
        }
    }

    func credential(for task: URLSessionTask.in protectionSpace: URLProtectionSpace) -> URLCredential? {
        dispatchPrecondition(condition: .onQueue(rootQueue))
        //HTTPS certificate processing
        return requestTaskMap[task]?.credential ??
            session.configuration.urlCredentialStorage?.defaultCredential(for: protectionSpace)
    }

    func cancelRequestsForSessionInvalidation(with error: Error?). {
        dispatchPrecondition(condition: .onQueue(rootQueue))
        // Throw an error when the URLSession fails
        requestTaskMap.requests.forEach { $0.finish(error: AFError.sessionInvalidated(error: error)) }
    }
}
Copy the code

X. Summary:

  • The entire Session class does the following:
    1. Initializes and manages the URLSession, and holds a SessionDelegate object to manage the related proxy methods of the NSURLSession.
    2. Initialize the different Request objects in three steps:
      • Depending on the parameters, different methods are dispatched to create different Request subclass objects
      • The Request object is preprocessed using interceptor adapters, which include default/global interceptors for the Session tape plus proprietary interceptors for each Request tape
      • Create an URLSessionTask subclass by calling the Request method and pairing Request with the URLSessionTask link
    3. Call Resume to send a task
    4. Implementing the RequestDelegate protocol provides two control parameters for creating Request objects, as well as handling retry logic
    5. Implements the SessionStateProvider protocol to process Request and Task states
  • Session just create Request, send Task, manage the mapping between Request and Task, Request creation, response processing, caching, HTTPS, OAuth2 authentication and so on will be completed slowly after learning the source code

Personal understanding record ~ if there is any mistake, please comment and point out, thank you very much ~