Alamofire (4)—— 你需要知道的細節(jié)

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


Alamofire 目錄直通車 --- 和諧學習禽最,不急不躁淘钟!


上一個篇章里面我們講解 SessionDelegate 是事件總響應者御蒲,我們根據(jù)不同的需求 (DataTaskDelegate、DownloadTaskDelegate蹦狂、UploadTaskDelegate痊乾、TaskDelegate)喘批,響應總代理然后根據(jù)需求的不同交給專業(yè)的人去做專業(yè)的事撩荣。耦合性大大降低,架構(gòu)的分層更加明顯! 這個篇章我要介紹 Alamofire 一些非常好用的小細節(jié)饶深,幫助大家在日后的開發(fā)里無往不利

一婿滓、SessionDelegate的對外閉包

我們的 SessionDelegate 不光是代理的總稱,同時也是我們對外邏輯強力輸出口粥喜,針對我們的代理響應提供了非常之多的閉包~??

// 接受到挑戰(zhàn)回調(diào)
open var sessionDidReceiveChallengeWithCompletion: ((URLSession, URLAuthenticationChallenge, @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)?
// 后臺事件完成的回調(diào)閉包
open var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)?
// 任務完成的閉包
open var taskDidComplete: ((URLSession, URLSessionTask, Error?) -> Void)?
// 下載讀寫的進度閉包
open var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
// 接收到事件任務數(shù)據(jù)的閉包
open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
// 接收到響應的閉包
open var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
  • 上面只是列舉了一些閉包凸主,但是你可以通過閉包的名字可以非常清晰感知作用
  • 下面舉個??
// 創(chuàng)建request
SessionManager.default.request(urlStr)
// 監(jiān)聽任務回調(diào)完成狀態(tài)
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 }
        // 回調(diào)完成閉包
        strongSelf.taskDidComplete?(session, task, error)
      }
  // 其他邏輯省略。额湘。卿吐。
}
  • 可以看到我們是可以直接從 SessionManager.default.delegate 直接對 taskDidComplete 的閉包聲明
  • 其實可以看到在 SessionDelegate 的代理響應里面執(zhí)行 taskDidComplete 閉包
  • 這樣對外提供閉包的本質(zhì):就是對外提供能力,讓開發(fā)人員更加自如锋华,方便

二嗡官、動態(tài)適配能力 - 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
    }
}
  • 實現(xiàn) RequestAdapter 協(xié)議的 adapt 方法
  • 對提供的 urlRequest 進行處理衍腥,比如統(tǒng)一配置 token
  • urlRequest 重定向,換一個新的 request 請求.
  • 記住一定要配置:SessionManager.default.adapter = LGAdapter()

三纳猫、自定義驗證

我們請求網(wǎng)絡(luò)習慣性 響應狀態(tài)碼200多 就是正確婆咸,其實我們可以根據(jù)自己公司的特性自定義處理驗證操作,更加符合實際開發(fā)

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 方法
  • 在閉包里面添加自己驗證方式
  • 比如沒有 數(shù)據(jù)data 我就返回 10089 錯誤
  • 狀態(tài)碼 == 404 的時候芜辕,我們返回 100800 錯誤
  • 其他返回成功尚骄。自定義的驗證根據(jù)自己特定需求處理,再一次感受到 Alamofire 的靈活

四侵续、重試請求

  • 重試請求的操作可能大家平時在開發(fā)里面運用不多倔丈,但是我覺得也是有需求場景的。作為相似處理状蜗,放到這里跟大家講解非常合適
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 也是繼承協(xié)議的處理方式,操作參考 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)
        // 一定要有出口,不然持續(xù)遞歸就會發(fā)生很嚴重的影響
        completion(false,0)
    }
}
  • 實現(xiàn) RequestRetrier 協(xié)議的 should
  • 記得使用:SessionManager.default.retrier = LGAdapter().

五蜀铲、Result

Alamofire 在請求數(shù)據(jù)會有一個 Response 但是這個不是我們最終的結(jié)果,還需要進過一層序列化属百。

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 是經(jīng)過了 responseSerializer.serializeResponse 序列化處理的結(jié)果
  • result 的結(jié)果最終傳入到了 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? {... }
}
  • 結(jié)果只有成功和失敗记劝,設(shè)計成了枚舉,Swift 枚舉非常強大 ??????族扰,這個地方也得以體現(xiàn)
  • 當然厌丑,為了打印更加詳細的信息,使Result實現(xiàn)了 CustomStringConvertibleCustomDebugStringConvertible協(xié)議 :
extension Result: CustomStringConvertible {
    public var description: String {
       // 就是返回 "SUCCESS" 和 "FAILURE" 的標識
    }
}
extension Result: CustomDebugStringConvertible {
    public var debugDescription: String {
        // 返回標識的同時渔呵,還返回了具體內(nèi)容   
    }
}
  • 下面還有很多其他方法的拓展怒竿,不是重點大家自己看看就OK

六、Timeline 時間軸

強大的Alamofire??????為了方便我們的開發(fā)還給我們提供了 Timeline 時間軸, 大家可以通過 Timeline 快速得到這個請求的時間數(shù)據(jù)扩氢,從而判斷請求是否合理耕驰,是否需要優(yōu)化....

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 
 }
  • 時間軸的數(shù)據(jù)得到,這里我們的Alamofire設(shè)計了一個隊列
self.queue = {
    let operationQueue = OperationQueue()

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

    return operationQueue
}()
  • 同步隊列為了讓流程順序執(zhí)行录豺。
  • 在剛初始化的時候當前隊列是掛起的 operationQueue.isSuspended = true
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
   // 省略無關(guān)代碼朦肘,方便閱讀
   // 請求完成,隊列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
       // 省略無關(guān)代碼媒抠,方便閱讀
    delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}

3:初始化響應時間

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

4:時間軸設(shè)置

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
  • 還有一些關(guān)鍵核心時間比如:startTime , initialResponseTime 就是在相關(guān)代理里面設(shè)置的趴生!
  • 如果沒有設(shè)置值,那么就在當時調(diào)用的時候重置當前時間
  • Timeline 的其他時間就是通過已知的 initialResponseTimerequestStartTime昏翰、requestCompletedTime 苍匆、serializationCompletedTime 計算得出!

這個篇章就先寫到這里吧棚菊!一不小心又是 01:40 ! 雖然這個點發(fā)出去锉桑,也不會有幾個人看了??????。但是我希望支持我的小伙伴??窍株,睡一覺醒來自然而然就能接受到來自 Cooci 給你文章推送通知民轴!一切的一切又是那么的美好??????,今夜睡去球订,期待美夢之后的奮斗后裸,加油~~~~~~??????

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市因苹,隨后出現(xiàn)的幾起案子苟耻,更是在濱河造成了極大的恐慌,老刑警劉巖扶檐,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凶杖,死亡現(xiàn)場離奇詭異,居然都是意外死亡款筑,警方通過查閱死者的電腦和手機智蝠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奈梳,“玉大人杈湾,你說我怎么就攤上這事∪列耄” “怎么了漆撞?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長于宙。 經(jīng)常有香客問我叫挟,道長,這世上最難降的妖魔是什么限煞? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任抹恳,我火速辦了婚禮,結(jié)果婚禮上署驻,老公的妹妹穿的比我還像新娘奋献。我一直安慰自己,他們只是感情好旺上,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布瓶蚂。 她就那樣靜靜地躺著,像睡著了一般宣吱。 火紅的嫁衣襯著肌膚如雪窃这。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天征候,我揣著相機與錄音杭攻,去河邊找鬼。 笑死疤坝,一個胖子當著我的面吹牛兆解,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跑揉,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼锅睛,長吁一口氣:“原來是場噩夢啊……” “哼埠巨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起现拒,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辣垒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后印蔬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勋桶,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年扛点,在試婚紗的時候發(fā)現(xiàn)自己被綠了哥遮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岂丘。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡陵究,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奥帘,到底是詐尸還是另有隱情铜邮,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布寨蹋,位于F島的核電站松蒜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏已旧。R本人自食惡果不足惜秸苗,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望运褪。 院中可真熱鬧惊楼,春花似錦、人聲如沸秸讹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽璃诀。三九已至弧可,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間劣欢,已是汗流浹背棕诵。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凿将,地道東北人年鸳。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像丸相,于是被迫代替她去往敵國和親搔确。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容