Alamofire 網(wǎng)絡(luò)請求流程探索2

本篇承接上一篇 Alamofire發(fā)起網(wǎng)絡(luò)請求初探 來繼續(xù)探索在網(wǎng)絡(luò)請求發(fā)起過程中涉及的一些有用的細(xì)節(jié).

RequestAdapter

RequestAdapter 是一個(gè)協(xié)議,提供了在發(fā)起請求之前,對request進(jìn)行最后處理的能力,先來看看具體使用:

 SessionManager.default.adapter = Adapter()

 SessionManager.default.request("your url", method: .get, parameters: ["p0":"111","p2":"222"])
            .response { (response) in
                debugPrint(response)
            }

class Adapter:RequestAdapter {
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var request = urlRequest
        request.setValue("aaa", forHTTPHeaderField: "aheader")
        return request
    }
}
  • 上面的例子設(shè)置了 Adapter , 所有在SessionManager.default發(fā)起請求之前,都會(huì)來到 Adapter 類adapt 方法中
  • 具體的使用場景可能有:
    添加公共的頭信息
    替換為一個(gè)全新的請求,但是這個(gè)請求的參數(shù)你可能要自己進(jìn)行序列化.來看源代碼:
open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
   var originalRequest: URLRequest?

     do {
       originalRequest = try urlRequest.asURLRequest()
       let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
          
         //這里傳入了 adapter 屬性
          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)
        }
}


struct Requestable: TaskConvertible {
        let urlRequest: URLRequest

        func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
            do {
               //在這里又傳遞了 adapt
                let urlRequest = try self.urlRequest.adapt(using: adapter)
                return queue.sync { session.dataTask(with: urlRequest) }
            } catch {
                throw AdaptError(error: error)
            }
        }
    }

extension URLRequest {
func adapt(using adapter: RequestAdapter?) throws -> URLRequest {
      //在這里判斷是否設(shè)置了 adapter
        guard let adapter = adapter else { return self }
        return try adapter.adapt(self)
    }
}

在使用 adapter 創(chuàng)建了 Task 之后,在后面就發(fā)起請求了. 并沒有走參數(shù)序列化的流程.

Timeline

Alamofire 在得到網(wǎng)絡(luò)的響應(yīng)后,會(huì)打印有關(guān)本次請求的時(shí)間信息.

Timeline: { 
"Request Start Time": 588155784.670, 
"Initial Response Time": 588155784.969,
"Request Completed Time": 588155784.972, 
"Serialization Completed Time": 588155784.972,
"Latency": 0.300 secs, 
"Request Duration": 0.302 secs, 
"Serialization Duration": 0.000 secs,
"Total Duration": 0.302 secs 
}

這里有許多有用的信息,比如Total Duration 代表一次請求從發(fā)起到接收到響應(yīng)的總時(shí)間,方便觀測接口性能.

這些時(shí)間的是怎么統(tǒng)計(jì)的呢?
  • 統(tǒng)計(jì)時(shí)間一般都是兩個(gè)絕對時(shí)間點(diǎn)做差,來看看Alamofire是如何做的.
open class Request {
  open func resume() {
        guard let task = task else { delegate.queue.isSuspended = false ; return }
        
       //startTime在 resume 之前被初始化,記錄時(shí)間點(diǎn)
        if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }

        task.resume()

        NotificationCenter.default.post(
            name: Notification.Name.Task.DidResume,
            object: self,
            userInfo: [Notification.Key.Task: task]
        )
    }


 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
        //endTime 在 Request 的父類中進(jìn)行了初始化
        delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
    }
}

open internal(set) var delegate: TaskDelegate {
        get {
            taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
            return taskDelegate
        }
        set {
            taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
            taskDelegate = newValue
        }
    }

值得注意的是 endTime 的初始化時(shí)機(jī), 被放到了 delegate.queue 中.來看看 delegate.queue

open class TaskDelegate: NSObject {

 init(task: URLSessionTask?) {
        _task = task

        self.queue = {
            let operationQueue = OperationQueue()

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

            return operationQueue
        }()
 }
}

可以看到 delegate.queue 是一個(gè)串行隊(duì)列,并且處于掛起狀態(tài). 所以想要使 RequestendTime的初始化方法被執(zhí)行, 需要了解 queue 何時(shí)取消掛起. queue取消掛起有兩處

open class TaskDelegate: NSObject {
     @objc(URLSession:task:didCompleteWithError:)
    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
        }
    }
}


open class 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]
        )
    }
}

取消掛起有兩個(gè)地方:

  • task 完成時(shí)
  • 請求將要發(fā)起時(shí),如果task創(chuàng)建失敗則沒有必要統(tǒng)計(jì)時(shí)間
具體是怎么被打印出來的?
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
 }

// DataRequset 繼承 Request
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 struct Timeline {
   
    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
    }
}
//這里是打印的格式化
extension Timeline: CustomStringConvertible {
  
    public var description: String {
        let latency = String(format: "%.3f", self.latency)
        let requestDuration = String(format: "%.3f", self.requestDuration)
        let serializationDuration = String(format: "%.3f", self.serializationDuration)
        let totalDuration = String(format: "%.3f", self.totalDuration)

        // NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is
        // fixed, we should move back to string interpolation by reverting commit 7d4a43b1.
        let timings = [
            "\"Latency\": " + latency + " secs",
            "\"Request Duration\": " + requestDuration + " secs",
            "\"Serialization Duration\": " + serializationDuration + " secs",
            "\"Total Duration\": " + totalDuration + " secs"
        ]

        return "Timeline: { " + timings.joined(separator: ", ") + " }"
    }
}


public struct DefaultDataResponse {
public init(
        request: URLRequest?,
        response: HTTPURLResponse?,
        data: Data?,
        error: Error?,
        timeline: Timeline = Timeline(),
        metrics: AnyObject? = nil)
    {
        self.request = request
        self.response = response
        self.data = data
        self.error = error
        self.timeline = timeline
    }
}

重試機(jī)制

有時(shí)一些比較重要的接口,如果請求失敗.失敗時(shí)需要重試. Alamofire 也優(yōu)雅地為我們提供了重試機(jī)制.

 SessionManager.default.retrier = Retrier()

 SessionManager.default.request("your url", method: .get, parameters: ["p0":"111","p2":"222"])
            .response { (response) in
                debugPrint(response)
            }

class Retrier {
    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)
        //在必要時(shí)調(diào)用,不然會(huì)引起無限重試
        completion(false,0)
    }
}
何時(shí)重試?
extension SessionDelegate: URLSessionTaskDelegate {
 open func 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)
        }
    }
}

}
open class SessionManager {
func retry(_ request: Request) -> Bool {
        guard let originalTask = request.originalTask else { return false }

        do {
            let task = try originalTask.task(session: session, adapter: adapter, queue: queue)

            if let originalTask = request.task {
                delegate[originalTask] = nil // removes the old request to avoid endless growth
            }

            request.delegate.task = task // resets all task delegate data

            request.retryCount += 1
            request.startTime = CFAbsoluteTimeGetCurrent()
            request.endTime = nil

            task.resume()

            return true
        } catch {
            request.delegate.error = error.underlyingAdaptError ?? error
            return false
        }
    }
}
  • open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) 方法中,檢測是否發(fā)生錯(cuò)誤.如果發(fā)生錯(cuò)誤就調(diào)用我們設(shè)置的retrier的 'should' 方法決定是否重試.
  • 根據(jù)我們設(shè)置的延時(shí)時(shí)間,延時(shí)一段時(shí)間后調(diào)用 SessionMangerfunc retry(_ request: Request) -> Bool 方法執(zhí)行真正的重試操作

響應(yīng)驗(yàn)證 (Response Validation)

SessionManager.default.request(urlStr, method: .get, parameters: ["p0":"aaa","p1":"bbb"])
            .response { (response) in
                debugPrint(response)
            }.validate { (request, response, data) -> Request.ValidationResult in
                guard let _ = data else{
                    return .failure(NSError.init(domain: "custom failure 0", code: 10089, userInfo: nil))
                }
                let code = response.statusCode
                if code == 404 {
                    return .failure(NSError.init(domain: "custom failure 1", code: 100800, userInfo: nil))
                }
                return .success
        }

validate 在得到 本次請求的響應(yīng) 之前被調(diào)用,在此處可以驗(yàn)證數(shù)據(jù)是否符合我們的業(yè)務(wù)邏輯,并向業(yè)務(wù)層返回自定義的錯(cuò)誤信息.
來看看源碼:

extension DataRequest {

@discardableResult
    public func validate(_ validation: @escaping Validation) -> Self {

       //創(chuàng)建一個(gè)閉包,在此閉包中調(diào)用 validation 參數(shù)閉包
        let validationExecution: () -> Void = { [unowned self] in
            if
                let response = self.response,
                self.delegate.error == nil,
                case let .failure(error) = validation(self.request, response, self.delegate.data)
            {
                self.delegate.error = error
            }
        }
       //添加驗(yàn)證閉包
        validations.append(validationExecution)

        return self
    }
}

此方法保存了 validation 閉包. 并且把返回的自定義error信息保存到 self.delegate.error 中 .

何時(shí)進(jìn)行驗(yàn)證?
extension SessionDelegate : URLSessionTaskDelegate {
   open func urlSession(_ session: URLSession, task: URLSessionTask, 
    didCompleteWithError error: Error?) {

            let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in
            guard let strongSelf = self else { return }

            strongSelf.taskDidComplete?(session, task, error)

            strongSelf[task]?.delegate.urlSession(session, task: task, didCompleteWithError: error)

            var userInfo: [String: Any] = [Notification.Key.Task: task]

            if let data = (strongSelf[task]?.delegate as? DataTaskDelegate)?.data {
                userInfo[Notification.Key.ResponseData] = data
            }

            NotificationCenter.default.post(
                name: Notification.Name.Task.DidComplete,
                object: strongSelf,
                userInfo: userInfo
            )

            strongSelf[task] = nil
        }

        ///省略...
       
        // Run all validations on the request before checking if an error occurred
        request.validations.forEach { $0() }

        // Determine whether an error has occurred
        var error: Error? = error

        if request.delegate.error != nil {
            error = request.delegate.error
        }

        ///省略...
        completeTask(session, task, error)
   }
}

是在每一次 DataTask 請求數(shù)據(jù)完成時(shí)調(diào)用保存的 validation 閉包, 并且把自定義的error信息放到 dataResponse 中最后返回給外界.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市芳来,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件持偏,死亡現(xiàn)場離奇詭異铡羡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)畜挨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來噩凹,“玉大人巴元,你說我怎么就攤上這事⊥匝纾” “怎么了逮刨?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我修己,道長恢总,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任睬愤,我火速辦了婚禮片仿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尤辱。我一直安慰自己砂豌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布光督。 她就那樣靜靜地躺著阳距,像睡著了一般。 火紅的嫁衣襯著肌膚如雪结借。 梳的紋絲不亂的頭發(fā)上筐摘,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音船老,去河邊找鬼咖熟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛柳畔,可吹牛的內(nèi)容都是我干的球恤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荸镊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了堪置?” 一聲冷哼從身側(cè)響起躬存,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舀锨,沒想到半個(gè)月后岭洲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坎匿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年盾剩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片替蔬。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡告私,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出承桥,到底是詐尸還是另有隱情驻粟,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布凶异,位于F島的核電站蜀撑,受9級(jí)特大地震影響挤巡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜酷麦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一矿卑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沃饶,春花似錦母廷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至轩褐,卻和暖如春椎咧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背把介。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國打工勤讽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拗踢。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓脚牍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親巢墅。 傳聞我的和親對象是個(gè)殘疾皇子诸狭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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