Alamofire(4)— 你須要知道的細節

😊😊😊Alamofire專題目錄,歡迎及時反饋交流 😊😊😊api


Alamofire 目錄直通車 --- 和諧學習,不急不躁!安全


上一個篇章裏面咱們講解 SessionDelegate 是事件總響應者,咱們根據不一樣的需求 (DataTaskDelegate、DownloadTaskDelegate、UploadTaskDelegate、TaskDelegate),響應總代理而後根據需求的不一樣交給專業的人去作專業的事。耦合性大大下降,架構的分層更加明顯! 這個篇章我要介紹 Alamofire 一些很是好用的小細節,幫助你們在往後的開發裏無往不利網絡

1、SessionDelegate的對外閉包

咱們的 SessionDelegate 不光是代理的總稱,同時也是咱們對外邏輯強力輸出口,針對咱們的代理響應提供了很是之多的閉包~😬session

// 接受到挑戰回調
open var sessionDidReceiveChallengeWithCompletion: ((URLSession, URLAuthenticationChallenge, @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)?
// 後臺事件完成的回調閉包
open var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)?
// 任務完成的閉包
open var taskDidComplete: ((URLSession, URLSessionTask, Error?) -> Void)?
// 下載讀寫的進度閉包
open var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
// 接收到事件任務數據的閉包
open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
// 接收到響應的閉包
open var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
複製代碼
  • 上面只是列舉了一些閉包,可是你能夠經過閉包的名字能夠很是清晰感知做用
  • 下面舉個🌰
// 建立request
SessionManager.default.request(urlStr)
// 監放任務回調完成狀態
SessionManager.default.delegate.taskDidComplete = { (session,task,error) in
    print("任務完成了")
}

// 背後的邏輯
open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    /// Executed after it is determined that the request is not going to be retried
    let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in
        guard let strongSelf = self else { return }
        // 回調完成閉包
        strongSelf.taskDidComplete?(session, task, error)
      }
  // 其餘邏輯省略。。。
}
複製代碼
  • 能夠看到咱們是能夠直接從 SessionManager.default.delegate 直接對 taskDidComplete 的閉包聲明
  • 其實能夠看到在 SessionDelegate 的代理響應裏面執行 taskDidComplete 閉包
  • 這樣對外提供閉包的本質:就是對外提供能力,讓開發人員更加自如,方便

2、動態適配能力 - RequestAdapter

這個功能特別好用,可以提供下面兩種能力。閉包

  • 1: request 處理
  • 2: request 重定向

下面咱們開始來玩玩這個適配能力架構

class LGAdapter: RequestAdapter{
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        // token
        // 1: request 處理
        // 2: request 重定向
        var request = urlRequest
        request.setValue("lgcoociToken", forHTTPHeaderField: "lgtoken")
        let newUrlRequest = URLRequest.init(url: URL(string: "http://www.douban.com/j/app/radio/channels")!)
        return newUrlRequest
    }
}
複製代碼
  • 實現 RequestAdapter 協議的 adapt 方法
  • 對提供的 urlRequest 進行處理,好比統一配置 token
  • urlRequest 重定向,換一個新的 request 請求.
  • 記住必定要配置:SessionManager.default.adapter = LGAdapter()

3、自定義驗證

咱們請求網絡習慣性 響應狀態碼200多 就是正確,其實咱們能夠根據本身公司的特性自定義處理驗證操做,更加符合實際開發app

SessionManager.default.request(urlStr, method: .get, parameters: ["username":"Kody","password":"888888"])
    .response { (response) in
        debugPrint(response)
    }.validate { (request, response, data) -> Request.ValidationResult in
        guard let _ = data else{
            return .failure(NSError.init(domain: "lgcooci", code: 10089, userInfo: nil))
        }
        let code = response.statusCode
        if code == 404 {
            return .failure(NSError.init(domain: "lgcooci", code: 100800, userInfo: nil))
        }
        return .success
}
複製代碼
  • 這段代碼裏面咱們在後面鏈式添加 validate 方法
  • 在閉包裏面添加本身驗證方式
  • 好比沒有 數據data 我就返回 10089 錯誤
  • 狀態碼 == 404 的時候,咱們返回 100800 錯誤
  • 其餘返回成功。自定義的驗證根據本身特定需求處理,再一次感覺到 Alamofire 的靈活

4、重試請求

  • 重試請求的操做可能你們平時在開發裏面運用很少,可是我以爲也是有需求場景的。做爲類似處理,放到這裏跟你們講解很是合適
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)
            }
        }
    }
}
複製代碼
  • SessionDelegate 完成請求的時候,判斷重試閉包是否存在,還有注意必定是錯誤的狀況,沒有錯誤沒有必要重連。這裏也透露出 retrier 搭配 validate 更美哦
  • retrier 也是繼承協議的處理方式,操做參考 adapter
extension LGAdapter: RequestRetrier{
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        print("manager = \(manager)")
        print("request = \(request)")
        print("error = \(error)")
        completion(true,1)
        // 必定要有出口,否則持續遞歸就會發生很嚴重的影響
        completion(false,0)
    }
}
複製代碼
  • 實現 RequestRetrier 協議的 should
  • 記得使用:SessionManager.default.retrier = LGAdapter().

5、Result

Alamofire 在請求數據會有一個 Response 可是這個不是咱們最終的結果,還須要進過一層序列化。dom

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
        )
    }
    return self
}
複製代碼
  • result 是通過了 responseSerializer.serializeResponse 序列化處理的結果
  • result 的結果最終傳入到了 dataResponse
public enum Result<Value> {
    case success(Value)
    case failure(Error)
    
   // 提供成功還有失敗的校驗
    public var isSuccess: Bool {... }
    public var isFailure: Bool {...}
    public var value: Value? {...}
    public var error: Error? {... }
}
複製代碼
  • 結果只有成功和失敗,設計成了枚舉,Swift 枚舉很是強大 🤙🤙🤙,這個地方也得以體現
  • 固然,爲了打印更加詳細的信息,使Result實現了 CustomStringConvertibleCustomDebugStringConvertible協議 :
extension Result: CustomStringConvertible {
    public var description: String {
       // 就是返回 "SUCCESS" 和 "FAILURE" 的標識
    }
}
extension Result: CustomDebugStringConvertible {
    public var debugDescription: String {
        // 返回標識的同時,還返回了具體內容 
    }
}
複製代碼
  • 下面還有不少其餘方法的拓展,不是重點你們本身看看就OK

6、Timeline 時間軸

強大的Alamofire👍👍👍爲了方便咱們的開發還給咱們提供了 Timeline 時間軸, 你們能夠經過 Timeline 快速獲得這個請求的時間數據,從而判斷請求是否合理,是否須要優化....post

timeline: Timeline: { 
"Request Start Time": 588099247.070,
"Initial Response Time": 588099272.474, 
"Request Completed Time": 588099272.475, 
"Serialization Completed Time": 588099272.475, 
"Latency": 25.404 secs, 
"Request Duration": 25.405 secs, 
"Serialization Duration": 0.000 secs, 
"Total Duration": 25.405 secs 
 }
複製代碼
  • 時間軸的數據獲得,這裏咱們的Alamofire設計了一個隊列
self.queue = {
    let operationQueue = OperationQueue()

    operationQueue.maxConcurrentOperationCount = 1
    operationQueue.isSuspended = true
    operationQueue.qualityOfService = .utility

    return operationQueue
}()
複製代碼
  • 同步隊列爲了讓流程順序執行。
  • 在剛初始化的時候當前隊列是掛起的 operationQueue.isSuspended = true
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
   // 省略無關代碼,方便閱讀
   // 請求完成,隊列resume
   queue.isSuspended = false
}
複製代碼
  • 請求完成,隊列 resume
  • 看到這裏也說明了加入這個隊列的任務必然在請求完成以後

1:請求開始時間學習

open func resume() {
    if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
}
複製代碼

2:添加請求完成時間記錄

init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
    self.session = session
       // 省略無關代碼,方便閱讀
    delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
複製代碼

3:初始化響應時間

func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
}
複製代碼

4:時間軸設置

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()
    )
}
複製代碼

5:初始化記錄時間以及計算時間

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
}
複製代碼
  • 看到這裏你也就知道爲何這些時間可以記錄,是由於不斷經過隊列同步控制,在一些核心的點保存當前時間! 好比:endTime
  • 還有一些關鍵核心時間好比:startTime , initialResponseTime 就是在相關代理裏面設置的!
  • 若是沒有設置值,那麼就在當時調用的時候重置當前時間
  • Timeline 的其餘時間就是經過已知的 initialResponseTimerequestStartTimerequestCompletedTimeserializationCompletedTime 計算得出!

這個篇章就先寫到這裏吧!一不當心又是 01:40 ! 雖然這個點發出去,也不會有幾我的看了🙁🙁🙁。可是我但願支持個人小夥伴👬,睡一覺醒來天然而然就能接受到來自 Cooci 給你文章推送通知!一切的一切又是那麼的美好😸😸😸,今夜睡去,期待好夢以後的奮鬥,加油~~~~~~💪💪💪

就問此時此刻還有誰?45度仰望天空,該死!我這無處安放的魅力!

相關文章
相關標籤/搜索