Alamofire 學習(三)-Request 上篇

1、簡介

前兩篇《Alamofire 學習(一)-網絡基礎知識準備》《Alamofire 學習(二)-URLSession 知識準備》,我惡補了一下網絡基礎知識和 URLSession 的知識,有須要的朋友能夠去看看。如今終於能夠開始正式學習 Alamofire 了。git

一、Alamofire 是什麼

Alamofireswift 寫的一個很是優秀的網絡請求框架,至關於 OC 中的 AFNetWork,並且與 AF 是同一家出的,其實 AFNetwork 的前綴 AF 即是 Alamofire 的縮寫。它本質是基於 URLSession 的封裝,讓咱們網絡請求相關代碼更簡潔易用。github 上截止如今已經 31.7k 顆⭐️了,附上 github 地址 Alamofiregithub

二、Alamofire 功能特性

  • 鏈式的請求/響應方法
  • URL / JSON / plist 參數編碼
  • 上傳類型支持:文件( File)、數據( Data)、流( Stream)以及 MultipartFormData
  • 支持文件下載,下載支持斷點續傳
  • 支持使用 NSURLCredential 進行身份驗證
  • HTTP 響應驗證
  • TLS Certificate and Public Key Pinning
  • Progress Closure & NSProgress

2、SesssionManager

咱們先來認識一個類 SesssionManagerSesssionManager 就是對外提供的管理者,這個管理者具有整個 Alamofire 的全部功能。swift

一、SesssionManager 的初始化

下面是 SesssionManager 初始化部分的源碼:api

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)
}
複製代碼

能夠看到它初始化了 session,其中 configuration 是默認了 .default 的模式,並將 代理移交,經過建立 SessionDelegate 這個專門處理代理的類來實現 URLSession 的代理。數組

二、代理完成回調

open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    sessionDidFinishEventsForBackgroundURLSession?(session)
}
複製代碼

3、後臺下載

上篇講了 URLSession 的後臺下載,如今再來看一下 Alamofire 的後臺下載吧。 我先封裝了一個後臺下載管理類的單例 , MYBackgroundManager網絡

struct MYBackgroundManager {
    static let shared = MYBackgroundManager()
    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)
    }()
}
複製代碼

⚠️注意: 一、設爲.background模式: 這裏的 configuration 必定要設置爲 .background 模式的(默認是**.default**),否則沒法實現後臺下載。session

二、作成單例: 這裏我把 manager 作成了單例,否則在進入後臺就會釋放,同時網絡也就會報錯:Error Domain=NSURLErrorDomain Code=-999 "cancelled",這樣在 AppDelegate 的回調方便接收。app

調用的時候很方便:框架

MYBackgroundManager.shared.manager
    .download(self.urlDownloadStr) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
    let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
    let fileUrl     = documentUrl?.appendingPathComponent(response.suggestedFilename!)
    return (fileUrl!,[.removePreviousFile,.createIntermediateDirectories])
    }
    .response { (downloadResponse) in
        print("下載回調信息: \(downloadResponse)")
    }
    .downloadProgress { (progress) in
        print("下載進度 : \(progress)")
}
複製代碼

AppDelegatehandleEventsForBackgroundURLSession 中用單例接收:ide

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    MYBackgroundManager.shared.manager.backgroundCompletionHandler = completionHandler
}
複製代碼

4、Request

Request 是一個父類,它有以下幾個子類:

  • DataRequest
  • DownloadRequest
  • UploadRequest
  • StreamRequest 不一樣的 request 有不一樣的做用,各司其職。

一、使用

使用起來很簡單,像下面這樣就實現了一個簡單的 get 請求

SessionManager.default.request(urlString, method: .get, parameters: ["username":"凡幾多"])
    .response { (response) in
        debugPrint(response)
}
複製代碼

二、源碼解析

點擊 request 進去,能夠看到源碼是下面這樣的:

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 = try encoding.encode(originalRequest!, with: parameters)
        return request(encodedURLRequest)
    } catch {
        return request(originalRequest, failedWith: error)
    }
}
複製代碼

能夠看到先是根據傳進來的 url、methodheaders 建立了一個 URLRequest,而後對 parameters 進行 URLEncoding 編碼,最後返回一個 DataRequest。 這裏咱們詳細看一下 encoding.encode 裏是如何編碼的, 這裏判斷了 method 的類型,

if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
複製代碼

(1)get 請求

若是是直接拼接到 url 後面的 method 類型(如 get),假設咱們的請求爲 get 請求,則執行下面的代碼:

let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
                urlComponents.percentEncodedQuery = percentEncodedQuery
                urlRequest.url = urlComponents.url
複製代碼

假設咱們的 get 請求爲:

http://www.douban.com/j/app/radio/channels?username="fanjiduo"&&password="123456"
複製代碼

那麼它的 urlComponents 是這樣的:

http://www.douban.com/j/app/radio/channels
  - scheme : "http"
  - host : "www.douban.com"
  - path : "/j/app/radio/channels"
複製代碼

咱們發現它是把路由部分 urlComponents 進行了百分號編碼:

(urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "")
複製代碼

而後對參數部分 parameters 也進行了百分號編碼:

query(parameters)
複製代碼

咱們點擊 query 進去看一下它的具體實現

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: "&")
}
複製代碼

一、先把參數根據 ASCII 進行了升序排序並遍歷:

for key in parameters.keys.sorted(by: <) {
    let value = parameters[key]!
複製代碼

二、把鍵值對經過 queryComponents 函數進行遞歸併進行百分號編碼,而後返回一個元祖,再把元祖添加到了數組 components 中

components += queryComponents(fromKey: key, value: value)
複製代碼

三、把元祖中的第一個元素和第二個元素用 = 鏈接,而後再用 & 符號進行了分割

return components.map { "\($0)=\($1)" }.joined(separator: "&")
複製代碼

(2)post 請求

若是是 post 請求,那麼 encoding.encode 又是如何編碼的呢? post 請求會在請求頭中多加一個 Content-Type

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

參數則再也不放到請求頭裏了,而是放在 urlRequest.httpBody中,而且進行了 .data 處理:

urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
複製代碼

三、task 和 request 的關係

接下來咱們分析一下內部:url -> request -> task 的過程。這個過程當中還創建了 task 以及 request 之間的綁定關係。 繼續上面的源碼分析:

return request(encodedURLRequest)
複製代碼

request 進去看一下源碼:

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)
    }
}
複製代碼

咱們看到這行代碼:

let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
複製代碼

點擊 Requestable 進去:

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)
        }
    }
}
複製代碼

咱們發現 Requestable 實際上是一個結構體,幫助 DataRequest 建立了一個包含 task 的結構體對象,至關於 DataRequest 的一個助理,幫 Requestable 建立了 task。 之因此這樣寫,而不直接把 task 建立寫在 DataRequest 中,是爲了下降耦合性。

  • 初始化 request 經過助理 Requestable 獲得結構體 originalTask 之後,就能夠獲得下面的 task,而且初始化出 request 了:
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
複製代碼

這個初始化方法是寫在父類 Request 裏的,

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() }
}
複製代碼

能夠看出這裏傳遞不一樣的枚舉值,初始化不一樣的 taskdelegate

  • 綁定 taskrequest 下面的代碼對 taskrequest 進行了綁定。方便在 SessionDelegate 下發任務,task 直接檢索,request 方便直接獲取。
delegate[task] = request
複製代碼

這一篇對 Request 有了大概的瞭解,下一篇咱們繼續。

轉載請備註原文出處,不得用於商業傳播——凡幾多

相關文章
相關標籤/搜索