Alamofire(4)— 你需要知道的细节

2,485 阅读7分钟

😊😊😊Alamofire专题目录,欢迎及时反馈交流 😊😊😊


Alamofire 目录直通车 --- 和谐学习,不急不躁!


上一个篇章里面我们讲解 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度仰望天空,该死!我这无处安放的魅力!