From the previous understanding and use of Alamofire, we can see that many features are designed around Request throughout the framework, so the Request Request is the core foundation of all requests. So let’s take a look at the important module Request.

Request process

First, let’s take a look at this code:

Alamofire.request("https://httpbin.org/get")
Copy the code

This is a very simple network Request. Let’s look at what’s going on in this Request. Enter the source code to view:

@discardableResult
public func request(
    _ url: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoding: ParameterEncoding = URLEncoding.default,
    headers: HTTPHeaders? = nil)
    -> DataRequest
{
    return SessionManager.default.request(
        url,
        method: method,
        parameters: parameters,
        encoding: encoding,
        headers: headers
    )
}
Copy the code

The request function also calls the request method of SessionManager, which indicates that the starting point of network request is from SessionManager. Obviously, Alamofire. Swift should be the top-level wrapper. The underlying file sessionManager.swift is then called. Ok, let’s move on:

@discardableResult
    open func request(
        _ url: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoding: ParameterEncoding = URLEncoding.default,
        headers: HTTPHeaders? = nil)
        -> DataRequest
    {
        var originalRequest: URLRequest?

        do {
            originalRequest = try URLRequest(url: url, method: method, headers: headers)
            let encodedURLRequest = tryencoding.encode(originalRequest! , with: parameters)return request(encodedURLRequest)
        } catch {
            return request(originalRequest, failedWith: error)
        }
    }
Copy the code

This function creates a Request object internally, encodes parameters to the Request, and then calls an internal Request function with parameters to the Request object. Here we go:

@discardableResult
    open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
        var originalRequest: URLRequest?

        do {
            originalRequest = try urlRequest.asURLRequest()
            let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)

            let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
            let request = DataRequest(session: session, requestTask: .data(originalTask, task))

            delegate[task] = request

            if startRequestsImmediately { request.resume() }

            return request
        } catch {
            return request(originalRequest, failedWith: error)
        }
    }
Copy the code
  • The permission of this function isopenTherefore, it can be usedSessionManager.default.requestTo initiate the request, however, the argument is_ urlRequest: URLRequestConvertible.
  • URLRequestConvertibleThe purpose of the agreement is toURLRequestPerform custom conversions, therefore, after obtaining convertedURLRequestAfter, need to useURLRequestgeneratetaskIn order to initiate a network requestAlamofireIn, but alwaysrequestAll of the functions at the beginning, by default, areDataRequestType, now you have itURLRequestNot enough. We need to see if she can produce its counterparttask.
  • In the function up here, it doesDataRequest.Requestable.RequestableIt’s actually a structure, and he implemented itTaskConvertibleProtocol, so it can be usedURLRequestGenerate the correspondingtask. Next we initializeDataRequest“And then actually start making requests.

OK, here’s a quick look at what the request process looks like:

Request

Request dependencies

The Request class is used to send network requests, receive response, associate data returned by the server, and manage tasks.

Request is the base class of DataRequest, DownloadRequest, UploadRequest, and StreamRequest.

// MARK: Properties

    /// The delegate for the underlying task.
    open internal(set) var delegate: TaskDelegate {
        get {
            taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
            return taskDelegate
        }
        set {
            taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
            taskDelegate = newValue
        }
    }

    /// The underlying task.
    open var task: URLSessionTask? { return delegate.task }

    /// The session belonging to the underlying task.
    public let session: URLSession

    /// The request sent or to be sent to the server.
    open var request: URLRequest? { returntask? .originalRequest }/// The response received from the server, if any.
    open var response: HTTPURLResponse? { returntask? .responseas? HTTPURLResponse }

    /// The number of times the request has been retried.
    open internal(set) var retryCount: UInt = 0

    let originalTask: TaskConvertible?

    var startTime: CFAbsoluteTime?
    var endTime: CFAbsoluteTime?

    var validations: [() -> Void] = []

    private var taskDelegate: TaskDelegate
    private var taskDelegateLock = NSLock(a)Copy the code

These properties are simple and well understood. Most importantly, let’s focus on the Request initialization method:

// MARK: Lifecycle

    init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
        self.session = session

        switch requestTask {
        case .data(let originalTask, let task):
            taskDelegate = DataTaskDelegate(task: task)
            self.originalTask = originalTask
        case .download(let originalTask, let task):
            taskDelegate = DownloadTaskDelegate(task: task)
            self.originalTask = originalTask
        case .upload(let originalTask, let task):
            taskDelegate = UploadTaskDelegate(task: task)
            self.originalTask = originalTask
        case .stream(let originalTask, let task):
            taskDelegate = TaskDelegate(task: task)
            self.originalTask = originalTask
        }

        delegate.error = error
        delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent()}}Copy the code

As you can see, to initiate a Request, we only need one task.

  • The first parametersessionUsed chiefly ofCustomStringConvertibleandCustomDebugStringConvertibleThe implementation of both protocols takes specific data.
  • The second parameter isrequestTaskThis is an enumeration type. Let’s see:
enum RequestTask {
        case data(TaskConvertible? .URLSessionTask?).case download(TaskConvertible? .URLSessionTask?).case upload(TaskConvertible? .URLSessionTask?).case stream(TaskConvertible? .URLSessionTask?). }Copy the code
  • We’re initializingRequestWhen, just need to passrequestTaskWith this enumerated value, we get two important pieces of data:RequestType and correspondingtask. The use of this technique greatly improves the quality of the code.
  • RequestTaskThere are two data bindings for enumerations and options,TaskConvertibleRepresents the original object that is implementedTaskConvertibleProtocol, which can be converted totask.URLSessionTaskIs the original object convertedtask. Hence the possibility of an advanced usage method that can be customized for a class to implementTaskConvertibleAgreement, you can manipulatetaskThe conversion process is very flexible.
delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent()}Copy the code

This line of code adds an operation to the agent’s queue. The queue is first-in-first-out, but operations inside the queue can be suspended via isSuspended.

About Parameter Coding

First, Alamofire supports the following encoding formats:

URLEncoding
JSONEncoding
PropertyListEncoding
Copy the code

Take a look at this code:

public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
    var urlRequest = try urlRequest.asURLRequest()

    guard let parameters = parameters else { return urlRequest }

    if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
        guard let url = urlRequest.url else {
            throw AFError.parameterEncodingFailed(reason: .missingURL)
        }

        if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
            let percentEncodedQuery = (urlComponents.percentEncodedQuery.map{$0 + "&"}????"") + query(parameters)
            urlComponents.percentEncodedQuery = percentEncodedQuery
            urlRequest.url = urlComponents.url
        }
    } else {
        if urlRequest.value(forHTTPHeaderField: "Content-Type") = =nil {
            urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
        }
        urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)}return urlRequest
}
Copy the code
  • Take out the request method, according to different request methods, parameter encoding is different,.get..head..deleteThe three methods are to concatenate the parameters directly after the URL, and the other methods are to concatenate the parameters directly after the URL.httpBody)
  • Our request is to passASCIICoded, so to do percent coding, the first step is to percent code all routes currently requested
  • Finally, parameters facilitate coding, splicing out

Look at:

private func query(_ parameters: [String: Any]) -> String {
    var components: [(String.String)] = []

    for key in parameters.keys.sorted(by: <) {
        let value = parameters[key]!
        components += queryComponents(fromKey: key, value: value)
    }
    return components.map { "\ [$0)=\ [$1)" }.joined(separator: "&")}Copy the code
  • throughASCIII sort from small to large,queryComponentsIn this method, the recursive parameters inside, and thekeyandvalueTake out, and then carry on the percent code, put into the tuple to save, form the parameter pair
  • Outside adds the tuple to the array
  • mapmapping($0) = \ ($1)
  • Type a separator between the elements&

** Finally note: ** Ordinary methods are concatenated directly after the URL. For example, the POST method puts these encoded parameter pairs into the request body, including the Content-Type header.

conclusion

The more you read and write, the more you realize that the functions in Alamofire are designed so well that they don’t all cascade together at once. Due to my limited knowledge level, I would like to point out any mistakes. Finally, I recommend Cooci’s article regularly.