Alamofire學習 -- Request補充

前言

經過上一篇內容學習了關於Request的基本內容,SessionManager管理RequestSessionDelegate的建立,並經過task綁定Request;Request管理請求的參數的配置編碼,建立taskTaskDelegate方法,而後SessionDelegate經過task將任務分發給 TaskDelegate,TaskDelegate代理執行任務的具體內容。下面對於不夠完善的地方再來作一丟丟補充🧠。json

Adapter-適配器

讓咱們把視線再拉回到上一篇中的SessionManager.swiftrequest方法: swift

來看👀,這裏在建立task的時候傳入了一個adapter參數,那麼這個adapter是幹嗎的?🤔api

看的出這是一個協議,而且在協議內部實現了一個 adapt方法,並且若是繼續跟進去 adapt方法,徹底看不到 adapt方法的具體實現,(偷個懶,就不截圖了😌😌😌)那麼既然這是一個協議,是否是須要用戶去實現呢?而且這個方法會放回一個 URLRequest,從上面的 request方法方法中已經知道存在了 URLRequest,那麼這裏爲甚麼還會返回呢?

其實也不難猜,既然是協議,並且adapt方法,傳入一個urlRequest,最後又返回URLRequest,那麼必然能夠在URLRequest設置參數,好比:Token,那麼下面就重寫這個adapt方法;bash

class ZHAdapter: RequestAdapter{
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var request = urlRequest
        request.setValue("XZXQWYEHNSDXXSCJHSJDSDSJD=", forHTTPHeaderField: "Token")
        request.setValue("iPhone", forHTTPHeaderField: "DeviceModel")
        return request
    }
}
複製代碼

寫個例子🌰試一下:服務器

let urlStr = "https://www.douban.com/j/app/radio/channels"
let url = URL.init(string: urlStr)!
Alamofire.SessionManager.default.adapter = ZHAdapter()
Alamofire.request(url,method: .post,parameters: ["Username":"Henry","Age":"18"]).responseJSON {
    (response) in
    switch response.result{
    case .success(let json):
        print("json:\(json)")
        break
    case .failure(let error):
        print("error:\(error)")
        break
    }
}
複製代碼

OK🙆‍♂️,搞定了。網絡

其實RequestAdapter這個協議還有另一個用法:重定向,直接返回一個新地址。session

class ZHAdapter: RequestAdapter{
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        let newURLRequest = URLRequest.init(url: URL.init(string: "https://www.douban.com/j/app/radio/channels")!)
        return newURLRequest
    }
}
複製代碼

總結🗣🗣🗣:閉包

首先實現 RequestAdapter協議的 adapt 方法 並對傳入的 urlRequest進行處理,好比配置 token等參數, 或者對urlRequest重定向,換一個新的 request 請求. 可是最重要的是必定要配置: Alamofire.SessionManager.default.adapter = ZHAdapter()app

validate-自定義驗證

在進行網絡請求時,通常狀況下,服務器會返回不一樣的狀態碼,而後拿到狀態碼來進行相應的任務,好比須要將某一結果404定義爲錯誤請求,那麼就要在error中來作處理,此時咱們可使用validate來從新驗證,並定義請求結果。dom

let urlStr = "https://www.douban.com/j/app/radio/channels"
let url = URL.init(string: urlStr)!
Alamofire.request(url,method: .post,parameters: ["Username":"Henry","Age":"18"]).responseJSON {
    (response) in
    switch response.result{
    case .success(let json):
        print("json:\(json)")
        break
    case .failure(let error):
        print("error:\(error)")
        break
    }
}.validate{ (request, response, data) -> Request.ValidationResult in
    print(response)
    guard let _ = data else {
        return .failure(NSError(domain: "你總說,是個人錯", code: 10000, userInfo: nil))
    }
    let code =  response.statusCode 
    if (code == 404 ){
        return .failure(NSError(domain: "錯錯錯,說個人錯,", code: 10010, userInfo: nil))
    }
    return .success
}
複製代碼

ok🙆‍♂️,再次搞定在這裏經過鏈式方法調用validate驗證方法,而後在閉包內部自定義驗證方式,而後根據不一樣的狀態碼來作相應的自定義處理。

retrier-從新請求

SessionDelegate 完成請求的時候,可是請求失敗的時候,會調用urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)方法,來看一下在這個方法裏retrier作了什麼處理

if let retrier = retrier, let error = error {
   retrier.should(sessionManager, retry: request, with: error) { [weak self] shouldRetry, timeDelay in
       guard shouldRetry else { completeTask(session, task, error) ; return }

       DispatchQueue.utility.after(timeDelay) { [weak self] in
           guard let strongSelf = self else { return }

           let retrySucceeded = strongSelf.sessionManager?.retry(request) ?? false

           if retrySucceeded, let task = request.task {
               strongSelf[task] = request
               return
           } else {
               completeTask(session, task, error)
           }
       }
   }
}
複製代碼

這裏會先判斷有沒有retrier,若是有就調用should方法,若是沒有就直接調用完成回調。經過源碼會發現retrier是繼承於RequestRetrier協議的類對象(與RequestAdapter相似)一樣須要本身來實現:

extension ZHRetrier: RequestRetrier{
   func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
       completion(true,1)
       //這裏不能讓它一直從新請求,須要有結束方法.
       completion(false,0)
   }
}
複製代碼

這裏should方法傳入四個參數,前三個參數很簡單,重點介紹⚔一下completion,completion有兩個參數shouldRetry爲是否請求,timeDelay爲延時請求的延時時間,因此在上面的代碼中寫告終束再次請求的方法completion(false,0).

let urlStr = "https://www.douban.com/j/app/radio/channels"
let url = URL.init(string: urlStr)!
Alamofire.SessionManager.default.retrier = ZHRetrier()
Alamofire.request(url,method: .post,parameters: ["Username":"Henry","Age":"18"]).responseJSON {
   (response) in
   switch response.result{
   case .success(let json):
       print("json:\(json)")
       break
   case .failure(let error):
       print("error:\(error)")
       break
   }
}.validate{ (request, response, data) -> Request.ValidationResult in
   print(response)
   guard let _ = data else {
       return .failure(NSError(domain: "你總說,是個人錯", code: 10000, userInfo: nil))
   }
   let code =  response.statusCode 
   if (code == 404 ){
       return .failure(NSError(domain: "錯錯錯,說個人錯,", code: 10010, userInfo: nil))
   }
   return .success
}
複製代碼

一樣最重要的是:Alamofire.SessionManager.default.retrier = ZHRetrier()

Timeline-時間軸

再次把視線拉回到文章的最開始的那副圖,是否是有這句代碼if startRequestsImmediately { request.resume() } 你會發現這裏是request.resume(),然而正常狀況下不該該是task.resume()嗎🙅‍♀️,由此可知,在這裏的request.resume()方法內部必然保存了task.resume()方法。跟進去看下:

這裏 resume()方法並無傳入參數,那麼必然會走到 else中去, delegate.queue.isSuspended = false ;若是沒有任務,隊列暫停掛起?

你這怕不是在逗我,搞得我好像不太聰明的亞子??????

有源碼可知當前這個delegateTaskDelegate,進入到TaskDelegate.swift源碼能夠發現queueOperationQueue,而且在TaskDelegateinit方法中實現了初始化。

能夠看到 queue做爲 TaskDelegate 的一個屬性,在初始化時成爲一個同步隊列,而且隊列是掛起的。 也就是說在發起 request以後,建立的 TaskDelegate會默認初始化一個隊列,而且把隊列掛起。

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let taskDidCompleteWithError = taskDidCompleteWithError {
            taskDidCompleteWithError(session, task, error)
        } else {
        //省略部分代碼
            queue.isSuspended = false
        }
    }
複製代碼

在這裏隊列就取消掛起了,這也就說明了加入到這個隊列中的任務都是在請求完成以後的。

OK🙆,下面帶着這個queue來看一下Timeline的具體實現:

1.startTime-網絡請求發起時間

resume方法中,會給 startTime 賦值當前時間戳,這就是網絡請求的發起時間

2.endTime-網絡請求結束時間

DataRequestinit方法中,會向 queue 隊列中添加一個任務,獲取當前的時間戳賦值給 endTime,這就是網絡請求結束時間。

可是由於此時當前隊列默認爲掛起狀態,因此不會執行裏面的任務。在網絡請求完成回調 didCompleteWithError 方法時會恢復 queue隊列queue.isSuspended = false,而後緊接着完成endTime賦值。

3.initialResponseTime-初始化響應時間

在網絡請求開始返回數據時,會設置 initialResponseTime 爲當前時間戳,這個時間就是初始化響應的時間。

4.TimeLine-時間軸設置

ResponseSerialization.swiftResponse 方法中,會向 queue隊列中添加一個任務,由於當前未使用自定義的序列化方法,因此直接返回請求回來的數據,而返回的數據中保存着 self.timeline.
因此在賦值 self.timeline 時,會初始化 Timeline 對象,對前面的時間作個記錄,並將當前時間戳做爲參數 serializationCompletedTime的值傳遞給 Timeline 對象。 然而這個 serializationCompletedTime 就是序列化結束的時間,同時這個任務也是在隊列恢復時執行。

5.初始化記錄時間以及計算總時間-totalDuration

在時間軸 TimeLine的初始化方法中,記錄了請求過程當中的操做時間點,並計算了每一個操做的時間間隔,在請求結束後返回至 ResponseSerializationresponse方法中。能夠看到整個時間軸 TimeLine上的操做都是經過同步隊列來保證的,同時也確保了操做時間的準確性。

借用Bo_Bo大佬的總結圖😀😺😁:

總結

關於RequestAdapter(適配器),validate(自定義驗證),retrier(從新請求),Timeline(時間軸)內容就學習到這裏了,我的感受仍是比較重要的,爲用戶的封裝使用提供了必定的便利性。

相關文章
相關標籤/搜索