Alamofire - 你需要知道的细节

作者: Cooci_和谐学习_不急不躁 | 来源:发表于2019-08-22 01:41 被阅读2次

    上一个篇章里面我们讲解 SessionDelegate 是事件总响应者,我们根据不同的需求 (DataTaskDelegate、DownloadTaskDelegate、UploadTaskDelegate、TaskDelegate),响应总代理然后根据需求的不同交给专业的人去做专业的事。耦合性大大降低,架构的分层更加明显! 这个篇章我要介绍 Alamofire 一些非常好用的小细节,帮助大家在日后的开发里无往不利

    一、SessionDelegate的对外闭包

    我们的 SessionDelegate 不光是代理的总称,同时也是我们对外逻辑强力输出口,针对我们的代理响应提供了非常之多的闭包~😬

    // 接受到挑战回调
    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 闭包
    • 这样对外提供闭包的本质:就是对外提供能力,让开发人员更加自如,方便

    二、动态适配能力 - 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()

    三、自定义验证

    我们请求网络习惯性 响应状态码200多 就是正确,其实我们可以根据自己公司的特性自定义处理验证操作,更加符合实际开发

    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 的灵活

    四、重试请求

    • 重试请求的操作可能大家平时在开发里面运用不多,但是我觉得也是有需求场景的。作为相似处理,放到这里跟大家讲解非常合适
    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().

    五、Result

    Alamofire 在请求数据会有一个 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
            )
        }
        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

    六、Timeline 时间轴

    强大的Alamofire👍👍👍为了方便我们的开发还给我们提供了 Timeline 时间轴, 大家可以通过 Timeline 快速得到这个请求的时间数据,从而判断请求是否合理,是否需要优化....

    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度仰望天空,该死!我这无处安放的魅力!

    相关文章

      网友评论

        本文标题:Alamofire - 你需要知道的细节

        本文链接:https://www.haomeiwen.com/subject/mntgsctx.html