A list,

The first two “Alamofire learning (I) – Preparation of basic network knowledge” and “Alamofire learning (II) – Preparation of URLSession knowledge”, I made up for the basic network knowledge and URLSession knowledge, friends who need to go to have a look. Now you can finally start learning about Alamofire in earnest.

1. What is Alamofire

Alamofire is a very excellent network request framework written by Swift, which is equivalent to AFNetWork in OC and derived from AF. In fact, the prefix AF of AFNetWork is the abbreviation of Alamofire. Its essence is based on URLSession encapsulation, so that we network request related code more concise and easy to use. Up to now, there have been 31.7k pieces ⭐️ on Github, attached to the github address Alamofire.

2. Functions and features of Alamofire

  • Chained request/response method
  • URL/JSON/PLIST parameter encoding
  • The upload types are File, Data, Stream, and MultipartFormData
  • Support file download, download support resumable breakpoint
  • Support for authentication using NSURLCredential
  • HTTP Response Validation
  • TLS Certificate and Public Key Pinning
  • Progress Closure & NSProgress

Second, the SesssionManager

Let’s start with a class called SesssionManager. SesssionManager is the externally provided manager that has all the functionality of Alamofire.

1. Initialization of SesssionManager

Here is the source code for the SesssionManager initialization section:

public init(
    configuration: URLSessionConfiguration = URLSessionConfiguration.default,
    delegate: SessionDelegate = SessionDelegate(),
    serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
    self.delegate = delegate
    self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)

    commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
Copy the code

As you can see, it initializes the session, with configuration defaulting to the.default mode, and hands over the proxy, implementing the URLSession proxy by creating a SessionDelegate class dedicated to handling the proxy.

2. The agent completes the callback

open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession){ sessionDidFinishEventsForBackgroundURLSession? (session) }Copy the code

Third, background download

I talked about the background download of URLSession, now let’s look at the background download of Alamofire. I first encapsulated a background download management class singleton, MYBackgroundManager:

struct MYBackgroundManager {
    static let shared = MYBackgroundManager(a)let manager: SessionManager = {
        let configuration = URLSessionConfiguration.background(withIdentifier: "com.lgcooci.AlamofireTest.demo")
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
        configuration.timeoutIntervalForRequest = 10
        configuration.timeoutIntervalForResource = 10        

        return SessionManager(configuration: configuration)
    }()
}
Copy the code

⚠️ Note: 1, set to. Background mode: The configuration must be set to. Background mode (the default is **. Default **), otherwise the background download cannot be achieved.

2, make singleton: here I make manager singleton, otherwise it will be released when entering the background, and the network will also report an error: ErrorDomain =NSURLErrorDomain Code=-999 “cancelled”, which is easy to receive in the AppDelegate callback.

It is convenient to call:

MYBackgroundManager.shared.manager
    .download(self.urlDownloadStr) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
    let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
    letfileUrl = documentUrl? .appendingPathComponent(response.suggestedFilename!)return(fileUrl! ,[.removePreviousFile,.createIntermediateDirectories]) } .response { (downloadResponse)in
        print("Download callback information:\(downloadResponse)")
    }
    .downloadProgress { (progress) in
        print("Download progress:\(progress)")}Copy the code

The AppDelegate using singleton handleEventsForBackgroundURLSession receiving:

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping (a) -> Void) {
    MYBackgroundManager.shared.manager.backgroundCompletionHandler = completionHandler
}
Copy the code

Fourth, the Request

Request is a parent class with the following subclasses:

  • DataRequest
  • DownloadRequest
  • UploadRequest
  • StreamRequest Different requests have different roles to perform.

1, use,

It is simple to use, implementing a simple GET request like this

SessionManager.default.request(urlString, method: .get, parameters: ["username":"How much more?"])
    .response { (response) in
        debugPrint(response)
}
Copy the code

2, source code analysis

Click request and you can see the source code looks like this:

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

You can see that a URLRequest is created based on the url, method, and headers passed in, then URLEncoding is applied to the parameters, and a DataRequest is returned. Encoding.encode (method) encoding.encode (method)

if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
Copy the code

(1) Get request

If it is a method type (such as GET) that is concatenated directly after the URL, we assume that our request is a GET request and execute the following code:

let percentEncodedQuery = (urlComponents.percentEncodedQuery.map{$0 + "&"}????"") + query(parameters)
                urlComponents.percentEncodedQuery = percentEncodedQuery
                urlRequest.url = urlComponents.url
Copy the code

Suppose our GET request is:

http://www.douban.com/j/app/radio/channels?username="fanjiduo"&&password="123456"
Copy the code

So its urlComponents look like this:

http://www.douban.com/j/app/radio/channels
  - scheme : "http"
  - host : "www.douban.com"
  - path : "/j/app/radio/channels"
Copy the code

We find that it encodes the routing part urlComponents as a percentage sign:

 (urlComponents.percentEncodedQuery.map{$0 + "&"}????"")
Copy the code

Then the percentage code of the parameters is also carried out:

query(parameters)
Copy the code

Let’s click query to see how this is implemented

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

1, the parameters according toASCIISort and traverse in ascending order:

for key in parameters.keys.sorted(by: <) {
    let value = parameters[key]!
Copy the code

QueryComponents (); queryComponents (); queryComponents ();

components += queryComponents(fromKey: key, value: value)
Copy the code

3. Connect the first element and the second element in the primitive with =, and then divide them with & :

return components.map { "\ [$0)=\ [$1)" }.joined(separator: "&")
Copy the code

(2) Post request

If it’s a POST request, how is encoding.encode encoded? Post requests add a content-type to the request header:

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

The parameters are no longer placed in the request header, but in the urlRequest.httpBody, with.data processing:

urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
Copy the code

3. The relationship between Task and request

Let’s look at the internal: URL -> request -> task process. This process also establishes a binding relationship between task and request. Continue the above source code analysis:

return request(encodedURLRequest)
Copy the code

Click request to see the source code:

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

We see this line of code:

let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
Copy the code

Click on Requestable to enter:

struct Requestable: TaskConvertible {
    let urlRequest: URLRequest
    
    func task(session: URLSession, adapter: RequestAdapter? , queue: DispatchQueue) throws -> URLSessionTask {
        do {
            let urlRequest = try self.urlRequest.adapt(using: adapter)
            return queue.sync { session.dataTask(with: urlRequest) }
        } catch {
            throw AdaptError(error: error)
        }
    }
}
Copy the code

We discovered that the Requestable is actually a structure that helps the DataRequest create a structure object containing the task, which is equivalent to a DataRequest assistant that helps the Requestable create the task. The reason for writing this, rather than writing the task creation directly in the DataRequest, is to reduce coupling.

  • OriginalTask (Requestable) {originalTask (Requestable) {originalTask (Requestable) {originalTask (Requestable);
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
Copy the code

This initialization method is written in the parent Request class,

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, different enumerations are passed and different tasks and delegates are initialized.

  • Binding task and Request The following code binds task and Request. It is convenient to send tasks to the SessionDelegate. Tasks can be retrieved directly and requests can be obtained directly.
delegate[task] = request
Copy the code

This article gives you an overview of Request, and we will continue in the next article.

Reprint please note the original source, shall not be used for commercial communication – any more