在前面源碼探索中,
SessionManager
管理Request
和SessionDelegate
的建立,並經過task
綁定Request
和SessionDelegate
對象;Request
負責請求的參數的配置,以及task
不一樣任務的建立,建立鏈接外部(發送請求對象)和TaskDelegate
的方法,經過閉包參數,獲取TaskDelegate
代理事件的內容;TaskDelegate
代理事件是由SessionDelegate
經過task
移交的。總結圖:前端
以上處理的目的是對任務作分層處理,使結構清晰。json
在Request
文件下還存在一個協議RequestAdapter
。在Manager
中建立調用。以下:swift
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
並無被初始化,怎麼回事呢?下面看一下是如何定義的:api
/// 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
等等這些公共參數,其實能夠設置的,那下面就來試試。網絡
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併發送一個請求:session
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
中設置了請求頭,那麼就運行一下,經過抓包看看公共參數是否添加成功:添加成功,開發中的參數之後就能夠單獨使用該方法進行管理了。閉包
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
}
}
複製代碼
直接修改原請求地址,重定向至其餘地址。併發
爲何會添加公共參數,或重定向?app
代碼追蹤,追蹤到最終使用位置以下:框架
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
對象傳出去加工處理。
開發中常常會根據不一樣的狀態碼來處理,好比開發中須要將某一結果定義爲錯誤請求,在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
又有了更多的瞭解,不管是監聽請求進度仍是這種驗證均以鏈式調用爲主,方便快捷。不少狀況下,若是網絡請求失敗,咱們是有從新請求的需求,那麼該框架也提供了這樣的方法,請求失敗都會調用代理方法: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)
}
}
}
複製代碼
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
屬性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
)
複製代碼
所以在請求結果中,咱們可以很方便的拿到全部咱們須要的信息。
爲何有時間軸,在網絡請求中,咱們須要準確的知道請求耗時,以便於前端或後臺作優化處理。下面就看一下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
}()
複製代碼
一、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
作了判空操做二、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
恢復隊列,恢復後上面提到的記錄時間任務便可加入到隊列中執行三、initialResponseTime-初始化響應時間
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
}
複製代碼
四、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: