一、回顧
在前面源碼探索中,
SessionManager
管理Request
和SessionDelegate
的創建,並通過task
綁定Request
和SessionDelegate
對象;Request
負責請求的參數的配置,以及task
不同任務的創建,創建連接外部(發送請求對象)和TaskDelegate
的方法,通過閉包參數,獲取TaskDelegate
代理事件的內容;TaskDelegate
代理事件是由SessionDelegate
通過task
移交的。總結圖:
以上處理的目的是對任務做分層處理,使結構清晰。
二、RequestAdapter-適配器
在Request
文件下還存在一個協議RequestAdapter
。在Manager
中創建調用。如下:
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)
}
}
聯繫上下文,adapter
並沒有被初始化,怎麼回事呢?下面看一下是如何定義的:
/// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary.
public protocol RequestAdapter {
/// Inspects and adapts the specified `URLRequest` in some manner if necessary and returns the result.
///
/// - parameter urlRequest: The URL request to adapt.
///
/// - throws: An `Error` if the adaptation encounters an error.
///
/// - returns: The adapted `URLRequest`.
func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}
一個協議內部定義了一個方法,上面定義可以以某種方式檢查並適應URLRequest
,實際是告訴我們,根據需要遵循該協議並實現該方法。一臉懵逼,實現它幹嘛呢?其實也不難猜測,既然給我們該類型,肯定是方便我們設置參數,如token、device、vision
等等這些公共參數,其實可以設置的,那下面就來試試。
1、添加公共參數
class MyAdapter: RequestAdapter{
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var request = urlRequest
request.setValue("hibotoken", forHTTPHeaderField: "token")
request.setValue("device", forHTTPHeaderField: "iOS")
request.setValue("vision", forHTTPHeaderField: "1.0.0")
return request
}
}
下面設置adapter併發送一個請求:
let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list"
let url = URL.init(string: urlStr)!
Alamofire.SessionManager.default.adapter = MyAdapter()
Alamofire.request(url,method: .post,parameters: ["page":"1","size":"20"]).responseJSON {
(response) in
switch response.result{
case .success(let json):
print("json:\(json)")
break
case .failure(let error):
print("error:\(error)")
break
}
}
- 在
SessionManager
中定義了adapter
對象,這裏就對其賦值一個實現了adapt
方法的子類對象
這裏在請求前在adapt
中設置了請求頭,那麼就運行一下,通過抓包看看公共參數是否添加成功:
添加成功,開發中的參數以後就可以單獨使用該方法進行管理了。
2、重定向
class redireatAdapter: RequestAdapter{
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
let newURLRequest = URLRequest.init(url: URL.init(string: "http://onapp.yahibo.top/public/?s=api/test")!)
return newURLRequest
}
}
直接修改原請求地址,重定向至其他地址。
爲什麼會添加公共參數,或重定向?
代碼追蹤,追蹤到最終使用位置如下:
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)
}
}
func adapt(using adapter: RequestAdapter?) throws -> URLRequest {
guard let adapter = adapter else { return self }
return try adapter.adapt(self)
}
這裏調用了該方法,這裏判斷了adapter
是否存在,不存在直接使用前面創建並設置好參數的URLRequest
對象,如果存在則adapter
調用adapt
方法,將當前URLRequest
對象傳出去加工處理。
三、validate-自定義驗證
開發中經常會根據不同的狀態碼來處理,比如開發中需要將某一結果定義爲錯誤請求,在error
中來做處理,那麼在該框架中我們可以使用validate
來重新驗證,並定義請求結果。代碼如下:
let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list2"
let url = URL.init(string: urlStr)!
Alamofire.request(url,method: .post,parameters: ["page":"1","size":"20"]).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: 0, userInfo: nil))
}
guard response.statusCode == 200 else {
return .failure(NSError(domain: "是不是哪弄錯了", code: response.statusCode, userInfo: nil))
}
return .success
}
- 通過鏈式方法調用
validate
驗證方法,根據具體需求添加驗證邏輯 - 返回數據爲空,定義爲錯誤信息
statusCode != 200
認爲是錯誤請求
通過以上試用,我們對Alamofire
又有了更多的瞭解,無論是監聽請求進度還是這種驗證均以鏈式調用爲主,方便快捷。
四、RequestRetrier-重新請求
很多情況下,如果網絡請求失敗,我們是有重新請求的需求,那麼該框架也提供了這樣的方法,請求失敗都會調用代理方法:urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
。而框架就在該代理方法中做了如下處理:
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)
}
}
}
} else {
completeTask(session, task, error)
}
- 當請求錯誤,先判斷
retrier
是否被定義如果定義則調用should
方法 - 這裏
retrier
是一個繼承自RequestRetrier
協議的類對象
RequestRetrier
/// A type that determines whether a request should be retried after being executed by the specified session manager
/// and encountering an error.
public protocol RequestRetrier {
/// Determines whether the `Request` should be retried by calling the `completion` closure.
///
/// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs
/// to be retried. The one requirement is that the completion closure is called to ensure the request is properly
/// cleaned up after.
///
/// - parameter manager: The session manager the request was executed on.
/// - parameter request: The request that failed due to the encountered error.
/// - parameter error: The error encountered when executing the request.
/// - parameter completion: The completion closure to be executed when retry decision has been determined.
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}
- 與
RequestAdapter
一樣,需要定義類並實現方法
創建子類並繼承協議,實現協議方法如下:
class MyRetrier: RequestRetrier{
var count: Int = 0
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
if count<3 {
completion(true,2)
count += 1
}else{
completion(false,2)
}
}
}
- 設置重新請求次數,爲3次
- 調用內部實現的閉包,向內傳值,告訴內部重新請求還是,終止請求
completion
有兩個參數shouldRetry
爲是否請求,timeDelay
爲延時請求的延時時間,這裏設置爲2秒- 延時請求避免,無效請求
下面就可以設置一個錯誤連接發送請求嘗試一下:
let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list2"
let url = URL.init(string: urlStr)!
Alamofire.SessionManager.default.retrier = MyRetrier()
Alamofire.request(url,method: .post,parameters: ["page":"1","size":"20"]).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: 10086, userInfo: nil))
}
if response.statusCode == 404 {
return .failure(NSError(domain: "密碼錯誤", code: response.statusCode, userInfo: nil))
}
return .success
}
- 和
RequestAdapter
使用方法一致,需要配置SessionManager的retrier
屬性
五、Response-響應結果
Alamofire
對請求到的數據進行了處理再返回給我們,以上請求我們都調用了responseJSON
方法來獲取最終數據,下面看一下responseJSON
內部做了哪些處理:
public func responseJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (DataResponse<Any>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.jsonResponseSerializer(options: options),
completionHandler: completionHandler
)
}
聯繫上文可知responseJSON
是DataRequest
的一個擴展方法,繼承自Request
類,因此可以進行鏈式調用。該方法內部繼續調用了response
方法。方法如下:
public func response<T: DataResponseSerializerProtocol>(
queue: DispatchQueue? = nil,
responseSerializer: T,
completionHandler: @escaping (DataResponse<T.SerializedObject>) -> Void)
-> Self
{
delegate.queue.addOperation {
let result = responseSerializer.serializeResponse(
self.request,
self.response,
self.delegate.data,
self.delegate.error
)
var dataResponse = DataResponse<T.SerializedObject>(
request: self.request,
response: self.response,
data: self.delegate.data,
result: result,
timeline: self.timeline
)
dataResponse.add(self.delegate.metrics)
(queue ?? DispatchQueue.main).async { completionHandler(dataResponse) }
}
return self
}
- 方法內部對請求結果進行了序列化處理
- 將序列化的結果封裝至
DataResponse
對象中
以上其實並沒有看到我們熟悉的序列化,再繼續搜索,找到如下代碼:
public static func serializeResponseJSON(
options: JSONSerialization.ReadingOptions,
response: HTTPURLResponse?,
data: Data?,
error: Error?)
-> Result<Any>
{
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) }
guard let validData = data, validData.count > 0 else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
}
do {
let json = try JSONSerialization.jsonObject(with: validData, options: options)
return .success(json)
} catch {
return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)))
}
}
- 序列化結果封裝至
Result
對象中 Result
對象最終封裝至DataResponse
對象中來管理
從上面代碼能夠發現response
對象管理了請求過程中所有參數:
var dataResponse = DataResponse<T.SerializedObject>(
request: self.request,
response: self.response,
data: self.delegate.data,
result: result,
timeline: self.timeline
)
因此在請求結果中,我們能夠很方便的拿到所有我們需要的信息。
六、Timeline-時間軸
爲什麼有時間軸,在網絡請求中,我們需要準確的知道請求耗時,以便於前端或後臺做優化處理。下面就看一下Alamofire
的時間軸是如何設計的。
首先我們能夠看到,任務是在隊列中執行的:
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)
}
}
隊列是在SessionManager
中創建,Manager
真是什麼都管啊。代碼如下:
let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)
- 設置標識綁定了當前設備的
UUID
- 該隊列是管理髮起的任務,和時間軸沒有關係
緊接着初始化TaskDelegate
對象。如下:
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)
}
}
通過.data(originalTask, task)傳入任務task,來初始化TaskDelegate對象如下:
self.queue = {
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
operationQueue.qualityOfService = .utility
return operationQueue
}()
- 設置最大併發量爲1,讓任務順序執行
- 初始化的隊列默認爲掛起狀態,因爲任務還沒有開啓
1、startTime-記錄發起請求時間
任務的創建與執行在Request中進行,代碼如下:
open func resume() {
guard let task = task else { delegate.queue.isSuspended = false ; return }
if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
task.resume()
NotificationCenter.default.post(
name: Notification.Name.Task.DidResume,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
- 該
resume
在SessionManager
中調用執行 - 判斷任務是否存在如果存在繼續執行,因爲有任務會被掛起,這裏重新啓動
task
不存在,說明任務已結束,隊列啓動執行其他任務- 啓動任務前記錄請求初始時間,因爲有掛起情況,這裏對
startTime
做了判空操作
2、endTimer-記錄請求結束時間
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() }
}
- 創建並分類任務代理,以便任務下發
- 記錄任務結束時間
上面代碼做了一個初始化,爲什麼說是結束時間呢,因爲隊列爲同步隊列,上次請求任務結束後纔會執行。即請求完成後,代碼如下:
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let taskDidCompleteWithError = taskDidCompleteWithError {
taskDidCompleteWithError(session, task, error)
} else {
if let error = error {
if self.error == nil { self.error = error }
if
let downloadDelegate = self as? DownloadTaskDelegate,
let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
{
downloadDelegate.resumeData = resumeData
}
}
queue.isSuspended = false
}
}
queue.isSuspended = false
恢復隊列,恢復後上面提到的記錄時間任務即可加入到隊列中執行
3、initialResponseTime-初始化響應時間
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
}
- 初始化數據響應時間,不同任務對應的都有初始化方法,如下載任務,上傳任務
4、TimeLine-時間軸設置
在響應初始化中,初始化時間軸:
extension DataRequest {
@discardableResult
public func response(queue: DispatchQueue? = nil, completionHandler: @escaping (DefaultDataResponse) -> Void) -> Self {
delegate.queue.addOperation {
(queue ?? DispatchQueue.main).async {
var dataResponse = DefaultDataResponse(
request: self.request,
response: self.response,
data: self.delegate.data,
error: self.delegate.error,
timeline: self.timeline
)
dataResponse.add(self.delegate.metrics)
completionHandler(dataResponse)
}
}
return self
}
}
- 時間軸是要面向開發的,因此在響應初始化時,被封裝至
Response
中
初始化時間軸,對前面的時間記錄做統一管理:
extension Request {
var timeline: Timeline {
let requestStartTime = self.startTime ?? CFAbsoluteTimeGetCurrent()
let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime
return Timeline(
requestStartTime: requestStartTime,
initialResponseTime: initialResponseTime,
requestCompletedTime: requestCompletedTime,
serializationCompletedTime: CFAbsoluteTimeGetCurrent()
)
}
}
時間軸初始化,計算請求間隔,序列化時間間隔:
public init(
requestStartTime: CFAbsoluteTime = 0.0,
initialResponseTime: CFAbsoluteTime = 0.0,
requestCompletedTime: CFAbsoluteTime = 0.0,
serializationCompletedTime: CFAbsoluteTime = 0.0)
{
self.requestStartTime = requestStartTime
self.initialResponseTime = initialResponseTime
self.requestCompletedTime = requestCompletedTime
self.serializationCompletedTime = serializationCompletedTime
self.latency = initialResponseTime - requestStartTime
self.requestDuration = requestCompletedTime - requestStartTime
self.serializationDuration = serializationCompletedTime - requestCompletedTime
self.totalDuration = serializationCompletedTime - requestStartTime
}
時間軸TimeLine
記錄了請求過程中的操作時間點,並計算了每部操作的時間間隔,在請求結束後封裝至Response
中。這裏通過隊列來同步請求中的操作,以保證startTime、endTime
的準確性,其他時間記錄是在請求代理回調中設置。
TimeLine: