在上一篇 Alamofire之request中部份內容寫得不是很清晰,因此在這一篇中進行補充。swift
在調用 Alamofire.request
時,最後會來到 SessionManager.request
以下代碼: api
那麼小夥伴是否主要到紅框中的代碼呢?數組
這種語法叫下標法。通常用於數組和字典中,那麼 delegate
做爲 SessionDelegate
的實例,是如何擁有這種能力的呢?服務器
這實際上是 Swift 語法的一種。若是重寫對象的 subscript
方法,就可使用下標法。在 SessionDelegate
中,咱們能夠看到以下代碼: 網絡
因此,能夠經過 SessionDelegate[task]
獲取到對應的 request
。session
那麼爲何Alamofire要經過 SessionDelegate
的 task
獲取其 request
呢?閉包
咱們知道,SessionDelegate
是面向開發者的協議集合,其內部實現了全部和URLSession有關的Delegate。可是真正處理任務的是 task
對應 request
的 delegate DownloadTaskDelegate、DataTaskDelegate、UploadTaskDelegate等來具體處理回調。dom
舉個栗子🌰:好比下載成功以後,須要將下載的文件移動到指定的目錄,這時會回調 SessionDelegate
實現的 func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)
方法。那麼必定就是 SessionDelegate
來處理嗎?答案是否認的。請看代碼:異步
這裏很明顯,分爲了兩步post
經過這種方式,實現 SessionDelegate
的任務分發功能。讓處理具體事務的 delegate 去處理對應的事務,避免了其內部邏輯混亂。
咱們在回到 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
呢?由於 adapter
是 SessionManager
的屬性,因此咱們能夠這樣:
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
,那麼咱們就能夠作不少事情了,好比:
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
}
複製代碼
還有其餘的一些用法,這裏就不一一舉例了。
在網絡請求返回結果以後,通常咱們還會對請求結果作一次驗證。
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
}
複製代碼
由於這一步須要服務器配合,因此請小夥伴自行驗證。
在網絡請求結束後,會回調 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次後再提示錯誤。
固然,這裏也不必定使用次數限制,小夥伴還可使用其餘方式控制是否重試。
仍是在 SessionManager.request
方法中,不知道小夥伴們是否有注意到這個地方:
咱們知道通常調用的是 task.resume()
,而這裏的 request.resume()
又是怎麼回事呢?咱們一塊兒來分析一下。
原來是裏面獲取了一個 task
,再調用 task.resume()
。
同時咱們還看到這樣一句代碼:delegate.queue.isSuspended = false
。在沒有任務時,讓delegate的隊列 queue
恢復。咦❓這是個什麼操做呢?這裏的隊列有什麼做用呢?
咱們先來查看一下源碼,看看這個 queue
究竟是什麼。
咱們看到這個 queue
是 OperationQueue
的實例。而且在下方 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
的賦值。
在網絡請求開始返回數據時,會設置 initialResponseTime
爲當前時間戳,這個時間就是初始化Response的時間。
在調用 response
方法時,會向 queue
隊列中添加一個任務。由於當前未使用自定義的序列化方法,因此直接返回請求回來的數據,並返回 self.timeline
。
由於 timeline
是一個計算屬性。
因此在調用 self.timeline
時,會初始化 Timeline
對象,並將當前時間戳做爲參數 serializationCompletedTime
的值傳遞給 Timeline
對象。
這個 serializationCompletedTime
就是序列化結束的時間。同時這個任務也是在隊列恢復時執行。
totalDuration = serializationCompletedTime - requestStartTime
便是當次網絡請求的總時長。
其他時間點則須要小夥伴自行探索咯。^_^
附Timeline時序圖
![]()
以上則是本篇的補充內容。如有不足之處,請評論指正。