One, a brief introduction

In Alamofire, in order to facilitate management and clarify division of labor, Alamofire makes a clear division of the whole request process and uniformly submits it to SessionManager for management. SessionManager is responsible for creating and managing objects such as SessionDelegate, URLSession, and URLRequest. Let’s look at an example request:

let urlStr = "http://onapp.yahibo.top/public/? s=api/test/list"
let url = URL.init(string: urlStr)!
Alamofire.request(url,method: .post,parameters: ["page":"1"."size":"20"]).responseJSON {
    (response) in
    switch response.result{
    case .success(let json):
        print("json:\(json)")
        break
    case .failure(let error):
        print("error:\(error)")
        break}}Copy the code

Request here is equivalent to a request API of Alamofire, which internally implements all required request configuration. In addition, request apis such as Download, Upload and Stream are encapsulated for direct use by users. The general request process is URL -> request -> Task -> Response. The general URL is the resource link of String type. Request is the most important part of our request. In Alamofire, the request process is subdivided into tasks. Let’s explore request as the main line to see how the framework is subdivided.

2. General configuration of URLRequest

Let’s look at the general configuration:

let url = URL.init(string: "1 protocol ://2 host address /3 one path /4 one & parameter 2")!
var request  = URLRequest.init(url: url)
request.httpMethod = HTTPMethod.post.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let postData = ["username":"hibo"."password":"123456"]
request.httpBody = try?JSONSerialization.data(withJSONObject: postData, options: [])
request.timeoutInterval = 30
request.cachePolicy = .useProtocolCachePolicy
Copy the code

A series of parameter configurations are required, and more configurations may be required according to different needs. Almost every page in the project needs network requests. For the above code amount, it must be encapsulated, extracted general code for reuse, and open configuration parameters for special needs.

3. URLRequest encapsulation

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
  • Url:Requesting a resource connection
  • Method: HTTPMethodEnumerates type parameters that set the request type defaultgettype
  • The Parameters:Request parameter, dictionary type, defaultnil
  • Encoding:Support encoding format, there are three types:URLEncoding, JSONEncoding, PropertyListEncodingThe defaultURLEncoding
  • Headers:The request header parameter Settings are null by default

The above are the general external parameters, which are set by users according to their requirements. The default value is provided internally. If the user does not set the default value (general attribute value), the default value will be enabled. These parameters are the attributes of the URLRequest, the main attributes in the request.

4. Parameter processing

In development, we often use GET or POST requests. A GET request whose parameters are an ampersand or/coincidence delimiter concatenated over an address, often used to retrieve data. POST requests encapsulate parameters in the body of the request and often submit data.

Let’s look at how the framework handles these parameters:

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
  • Returns with no argumentsURLRequestobject
  • If there are parameters, the parameters need to be processed separately.GETYou have to splice,POSTNeed to add add tohttpBodyIn the request body
  • percentEncodedQueryObtain the parameters after the domain name and concatenate them with the request parameters

The query function concatenates the parameters. As follows:

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
  • Pass in a dictionary argument
  • The dictionarykeytheASCIISort and recombine into a tuple array
  • queryComponentsThe internal recursive operation will bekeyandvalueDo the percent code and put it back into the tuple
  • throughmapCombine tuple elements to form a new array in passjoinedThe concatenate () function concatenates the elements of an array&Are separated

queryComponents

public func queryComponents(fromKey key: String, value: Any)- > [(String.String)] {
    var components: [(String.String)] = []

    if let dictionary = value as? [String: Any] {
        for (nestedKey, value) in dictionary {
            components += queryComponents(fromKey: "\(key)[\(nestedKey)]. "", value: value)
        }
    } else if let array = value as? [Any] {
        for value in array {
            components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
        }
    } else if let value = value as? NSNumber {
        if value.isBool {
            components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue))))
        } else {
            components.append((escape(key), escape("\(value)")))}}else if let bool = value as? Bool {
        components.append((escape(key), escape(boolEncoding.encode(value: bool))))
    } else {
        components.append((escape(key), escape("\(value)")))}return components
}
Copy the code
  • Recursive operation, iterating through each layer of the data until the key-value pair is of non-dictionary and array type, starting the pairkeyandvalueDo a percent code operation
  • escapeIs the encoding function

A GET request

Query to concatenate all key value pairs into the form “username=hibo&password=123456”, directly concatenate request domain name, request. Url is the domain name, . Through the above urlComponents percentEncodedQuery has split out the resource path as a result, splicing connection is: host + resource path (/ test/list/s = API) + & + parameters

A POST request

Give default configuration without content-Type configuration. As follows:

urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8".forHTTPHeaderField: "Content-Type")
Copy the code

Convert the concatenated query parameters to Data, which is passed to the request body httpBody. At this point, the parameters for GET or other requests are set. Once URLRequest is set up, you can create tasks to send requests. Now let’s see what we can do with the frame.

Send a request

Follow the code to find another request method:

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

There are only three steps:

  1. In this method, instead of initiating a request, session and request for initiating a task are handed over to the DataRequest. As for task stratification, the manager said, I just maintain the parameters you need and take the specific tasks home to do, with a clear division of labor and clearer thinking.

  2. Binding The binding returns a task and a Request so that the SessionDelegate can deliver the task. The Task delegate object is obtained from the Task in the SessionDelegate

  3. Start request.resume() with manager to manage tasks

DataRequest

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
  • Enumerations are used to configure different task agents

TaskDelegate

Divided into a number of subclasses, with different functions. The subclasses are as follows:

DataTaskDelegate DownloadTaskDelegate UploadTaskDelegate

According to different task types, the Request class determines the different processing classes, while the stream class has the base class to handle the tasks. Closures of the corresponding broker events are declared externally for passing broker messages. As follows:

var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
Copy the code

These closures are implemented in the Request class or extension method and are not actually implemented but bridge to the interface implementation so that the proxy event passes directly to the interface closure.

conclusion

1, our SessionManager

  • httpAdditionalHeadersParameter configuration
  • createURLRequest
  • createURLSession
  • URLSessionDelegateDelegate toSessionDelegate
  • Initiating a task request

2, SessionDelegate

  • Implement proxy methods for all network requests and declare closures through which proxy response events are passed
  • If no external closure is implemented to receive a proxy event, the proxy event is passedtaskSign over toTaskDelegateA specific subclass of the

3, the Request

  • Responsible for task creation and retentionURLSessionThe object isURLRequestobject
  • Responsible for the task delivery, delivery toTaskDelegateIn the
  • Implement data transfer method, external set closure parameters, internal closure bridge toTaskDelegateIn, that is, implementationTaskDelegateClosure declared externally in
  • Method returnsselfImplement chain calls

4, TaskDelegate

  • Create subclasses, responsible for specific task implementation
  • Externally declared closures pass out broker messages
  • The modified object is inRequestThe use of