Alamofire之request補充

上一篇 Alamofire之request中部份內容寫得不是很清晰,因此在這一篇中進行補充。swift

1、下標法

在調用 Alamofire.request 時,最後會來到 SessionManager.request 以下代碼: api

那麼小夥伴是否主要到紅框中的代碼呢?數組

這種語法叫下標法。通常用於數組和字典中,那麼 delegate 做爲 SessionDelegate 的實例,是如何擁有這種能力的呢?服務器

這實際上是 Swift 語法的一種。若是重寫對象的 subscript 方法,就可使用下標法。在 SessionDelegate 中,咱們能夠看到以下代碼: 網絡

因此,能夠經過 SessionDelegate[task] 獲取到對應的 requestsession

那麼爲何Alamofire要經過 SessionDelegatetask 獲取其 request 呢?閉包

咱們知道,SessionDelegate 是面向開發者的協議集合,其內部實現了全部和URLSession有關的Delegate。可是真正處理任務的是 task 對應 request 的 delegate DownloadTaskDelegateDataTaskDelegateUploadTaskDelegate等來具體處理回調。dom

舉個栗子🌰:好比下載成功以後,須要將下載的文件移動到指定的目錄,這時會回調 SessionDelegate實現的 func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) 方法。那麼必定就是 SessionDelegate 來處理嗎?答案是否認的。請看代碼:異步

這裏很明顯,分爲了兩步post

  • 若是開發者已經給 downloadTaskDidFinishDownloadingToURL 賦值了,那麼就回調這個閉包。
  • 若是沒有閉包,則經過 self[downloadTask]?.delegate 獲取到對應task的delegate,讓delegate去處理回調。

經過這種方式,實現 SessionDelegate 的任務分發功能。讓處理具體事務的 delegate 去處理對應的事務,避免了其內部邏輯混亂。

2、RequestAdapter

咱們在回到 SessionManager.request 中。

小夥伴看到這個 adapter 是否是又以爲有點懵?😳

這個 adapter 能幹什麼呢?咱們先看看這個屬性的是個什麼鬼👻? 而後咱們就看到了 open var adapter: RequestAdapter?

並且很奇怪的是也沒有初始化,那咱們再看看 RequestAdapter 是什麼。

這是一個協議。這個協議能夠作什麼呢?既然Alamofire沒有實現,那麼必然須要咱們本身來實現。

咱們來舉個栗子🌰: 咱們建立一個類 BOAdapter 並繼承自 RequestAdapter 協議。 協議要求必須實現一個 adapt 方法。

class BOAdapter: RequestAdapter {
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        
        return urlRequest
    }
}
複製代碼

adapt 方法傳入一個 URLRequest 並返回一個 URLRequest。咱們先不對其作任何處理,直接返回這個 urlRequest,而後再調試一下,這個 urlReqeust 究竟是什麼。

咱們該如何使用這個 BOAdapter 呢?由於 adapterSessionManager 的屬性,因此咱們能夠這樣:

let urlBD = "https://www.baidu.com"

// 使用實現的 adapter
Alamofire.SessionManager.default.adapter = BOAdapter()
Alamofire.request(urlBD, method: .get, parameters: ["user": "bo"]).response { (response) in
    print(response)
}
複製代碼

運行,調試,能夠知道,urlRequest 就是咱們請求發起的 urlRequest。咱們能拿到這個 request,那麼咱們就能夠作不少事情了,好比:

一、給request添加公共參數

func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
    
    var request = urlRequest
    
    // 設置參數
    request.setValue("token", forHTTPHeaderField: "token")
    
    return urlRequest
}
複製代碼

二、重定向

func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
    
    // 若是是百度地址開頭,則重定向到QQ地址
    if let url = urlRequest.url?.absoluteString, url.hasPrefix("https://www.baidu.com") {
        
        let request = URLRequest(url: URL(string: "https://www.qq.com")!)
        
        return request
    }
    
    return urlRequest
}
複製代碼

還有其餘的一些用法,這裏就不一一舉例了。

3、validate

在網絡請求返回結果以後,通常咱們還會對請求結果作一次驗證。

let urlBD = "https://www.baidu.com"

Alamofire.request(urlBD, method: .get, parameters: ["user": "bo"])
    .response { (response) in
        print(response)
    }.validate { (request, response, data) -> Request.ValidationResult in
        
        guard let _ = data else {
            
            return .failure(NSError(domain: "NO Response", code: 10086, userInfo: nil))
        }
        
        guard response.statusCode == 200 else {
            
            return .failure(NSError(domain: "Error", code: response.statusCode, userInfo: nil))
        }
        
        return .success
}
複製代碼

由於這一步須要服務器配合,因此請小夥伴自行驗證。

4、RequestRetrier

在網絡請求結束後,會回調 func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) 方法。 在這個方法中,咱們會看到下面這段代碼:

經過註釋,咱們知道這段代碼的做用是:若是發生錯誤並設置了 retrier,則異步詢問 retrier 是否應重試請求。不然,經過通知 delegate 來完成任務。

那麼這個 retrier 又該如何設置呢?查看這個屬性 var retrier: RequestRetrier? 並無初始化,因此,也須要咱們本身來設置。

再查看 RequestRetrier:

這是一個協議。而且須要咱們本身來實現。

舉個栗子🌰: 咱們自定義一個 BORetrier 繼承自 RequestRetrier協議。協議要求實現 should 方法。

should 方法有四個參數,前三個參數都比較好理解。可是第四個參數是一個閉包,這又是什麼呢?咱們查看其源碼:

經過註釋咱們知道:這個一個閉包,並在 RequestRetrier 決定是否一個 request 應該被重試的時候執行。

說明咱們能夠經過這個閉包,控制是否重試當前的請求。

class BORetrier: RequestRetrier {
    
    var count: Int = 0
    
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        
        print("manager = \(manager)")
        print("request = \(request)")
        print("error = \(error)")
        
        // 最多重試3次
        if count < 3 {
            completion(true, 1)
            count += 1
        } else {
            completion(false, 0)
        }
    }
}
複製代碼

在咱們自定義的 BORetrier 中,咱們規定,重試次數不超過3次。

咱們能夠以下面的代碼這樣使用 BORetrier

let urlBD = "https://www.xxxxxx.com"

Alamofire.SessionManager.default.retrier = BORetrier()
Alamofire.request(urlBD, method: .get, parameters: ["user": "bo"])
    .response { (response) in
        print(response)
    }.validate { (request, response, data) -> Request.ValidationResult in
        
        guard let _ = data else {
            
            return .failure(NSError(domain: "NO Response", code: 10086, userInfo: nil))
        }
        
        guard response.statusCode == 200 else {
            
            return .failure(NSError(domain: "Error", code: response.statusCode, userInfo: nil))
        }
        
        return .success
}
複製代碼

運行,由於地址 www.xxxxxx.com 沒法訪問,因此請求返回錯誤。可是由於有重試機制,因此會在重試3次後再提示錯誤。

固然,這裏也不必定使用次數限制,小夥伴還可使用其餘方式控制是否重試。

5、queue

仍是在 SessionManager.request 方法中,不知道小夥伴們是否有注意到這個地方:

咱們知道通常調用的是 task.resume(),而這裏的 request.resume()又是怎麼回事呢?咱們一塊兒來分析一下。

原來是裏面獲取了一個 task,再調用 task.resume()

同時咱們還看到這樣一句代碼:delegate.queue.isSuspended = false。在沒有任務時,讓delegate的隊列 queue 恢復。咦❓這是個什麼操做呢?這裏的隊列有什麼做用呢?

咱們先來查看一下源碼,看看這個 queue 究竟是什麼。

咱們看到這個 queueOperationQueue 的實例。而且在下方 TaskDelegate 的初始化方法咱們能夠找到 queue 的初始化。

maxConcurrentOperationCount = 1 代表 queue 是一個串行隊列。isSuspended = true 代表這個隊列默認是掛起的。

那麼這個串行隊列到底能作什麼呢?這就和網絡請求的 Timeline 有關了。

小夥伴都知道,在response 方法中,打印 response 會打印出請求結果,在其中會有這樣一段字符串 Timeline: { "Request Start Time": 588156668.544, "Initial Response Time": 588156668.835, "Request Completed Time": 588156668.838, "Serialization Completed Time": 588156668.838, "Latency": 0.291 secs, "Request Duration": 0.294 secs, "Serialization Duration": 0.000 secs, "Total Duration": 0.295 secs } 這就是網絡請求的 Timeline,標識出了網絡請求的起始時間等時間點。那麼,這是如何實現的呢?

一、網絡請求發起時間

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

二、網絡請求結束時間

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

可是由於當前隊列默認爲掛起狀態,因此不會執行裏面的任務。那麼這個任務在什麼時候執行呢?

在網絡請求完成回調 didCompleteWithError 方法時會恢復 queue 隊列。

這時就會執行 queue 隊列內的任務,完成 endTime 的賦值。

三、初始化Response時間

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

四、序列化結束時間

在調用 response 方法時,會向 queue 隊列中添加一個任務。由於當前未使用自定義的序列化方法,因此直接返回請求回來的數據,並返回 self.timeline

由於 timeline 是一個計算屬性。

因此在調用 self.timeline 時,會初始化 Timeline 對象,並將當前時間戳做爲參數 serializationCompletedTime 的值傳遞給 Timeline 對象。

這個 serializationCompletedTime 就是序列化結束的時間。同時這個任務也是在隊列恢復時執行。

五、請求總時長

totalDuration = serializationCompletedTime - requestStartTime 便是當次網絡請求的總時長。

其他時間點則須要小夥伴自行探索咯。^_^

附Timeline時序圖


以上則是本篇的補充內容。如有不足之處,請評論指正。

相關文章
相關標籤/搜索