Alamofire-Request補充

一、回顧

在前面源碼探索中,SessionManager管理RequestSessionDelegate的創建,並通過task綁定RequestSessionDelegate對象;Request負責請求的參數的配置,以及task不同任務的創建,創建連接外部(發送請求對象)和TaskDelegate的方法,通過閉包參數,獲取TaskDelegate代理事件的內容;TaskDelegate代理事件是由SessionDelegate通過task移交的。總結圖:

SessionManager.png

以上處理的目的是對任務做分層處理,使結構清晰。

二、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中設置了請求頭,那麼就運行一下,通過抓包看看公共參數是否添加成功:

args.png

添加成功,開發中的參數以後就可以單獨使用該方法進行管理了。

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
    )
}

聯繫上文可知responseJSONDataRequest的一個擴展方法,繼承自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]
    )
}
  • resumeSessionManager中調用執行
  • 判斷任務是否存在如果存在繼續執行,因爲有任務會被掛起,這裏重新啓動
  • 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:

timeline.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章