美文网首页
Alamofire学习 -- Request补充

Alamofire学习 -- Request补充

作者: Henry_Jeannie | 来源:发表于2019-08-25 01:39 被阅读0次

    前言

    通过上一篇内容学习了关于Request的基本内容,SessionManager管理RequestSessionDelegate的创建,并通过task绑定Request;Request管理请求的参数的配置编码,创建taskTaskDelegate方法,然后SessionDelegate通过task将任务分发给 TaskDelegate,TaskDelegate代理执行任务的具体内容。下面对于不够完善的地方再来做一丢丢补充🧠。

    Adapter-适配器

    让我们把视线再拉回到上一篇中的SessionManager.swiftrequest方法:

    来看👀,这里在创建task的时候传入了一个adapter参数,那么这个adapter是干嘛的?🤔


    看的出这是一个协议,并且在协议内部实现了一个adapt方法,而且如果继续跟进去adapt方法,完全看不到adapt方法的具体实现,(偷个懒,就不截图了😌😌😌)那么既然这是一个协议,是不是需要用户去实现呢?并且这个方法会放回一个URLRequest,从上面的request方法方法中已经知道存在了URLRequest,那么这里为甚么还会返回呢?

    其实也不难猜,既然是协议,而且adapt方法,传入一个urlRequest,最后又返回URLRequest,那么必然可以在URLRequest设置参数,比如:Token,那么下面就重写这个adapt方法;

    class ZHAdapter: RequestAdapter{
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
            var request = urlRequest
            request.setValue("XZXQWYEHNSDXXSCJHSJDSDSJD=", forHTTPHeaderField: "Token")
            request.setValue("iPhone", forHTTPHeaderField: "DeviceModel")
            return request
        }
    }
    

    写个例子🌰试一下:

    let urlStr = "https://www.douban.com/j/app/radio/channels"
    let url = URL.init(string: urlStr)!
    Alamofire.SessionManager.default.adapter = ZHAdapter()
    Alamofire.request(url,method: .post,parameters: ["Username":"Henry","Age":"18"]).responseJSON {
        (response) in
        switch response.result{
        case .success(let json):
            print("json:\(json)")
            break
        case .failure(let error):
            print("error:\(error)")
            break
        }
    }
    

    OK🙆‍♂️,搞定了。

    其实RequestAdapter这个协议还有另外一个用法:重定向,直接返回一个新地址。

    class ZHAdapter: RequestAdapter{
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
            let newURLRequest = URLRequest.init(url: URL.init(string: "https://www.douban.com/j/app/radio/channels")!)
            return newURLRequest
        }
    }
    

    总结🗣🗣🗣:

    首先实现 RequestAdapter协议的 adapt 方法
    并对传入的 urlRequest进行处理,比如配置 token等参数,
    或者对urlRequest重定向,换一个新的 request 请求.
    但是最重要的是一定要配置: Alamofire.SessionManager.default.adapter = ZHAdapter()

    validate-自定义验证

    在进行网络请求时,一般情况下,服务器会返回不同的状态码,然后拿到状态码来进行相应的任务,比如需要将某一结果404定义为错误请求,那么就要在error中来做处理,此时我们可以使用validate来重新验证,并定义请求结果。

    let urlStr = "https://www.douban.com/j/app/radio/channels"
    let url = URL.init(string: urlStr)!
    Alamofire.request(url,method: .post,parameters: ["Username":"Henry","Age":"18"]).responseJSON {
        (response) in
        switch response.result{
        case .success(let json):
            print("json:\(json)")
            break
        case .failure(let error):
            print("error:\(error)")
            break
        }
    }.validate{ (request, response, data) -> Request.ValidationResult in
        print(response)
        guard let _ = data else {
            return .failure(NSError(domain: "你总说,是我的错", code: 10000, userInfo: nil))
        }
        let code =  response.statusCode 
        if (code == 404 ){
            return .failure(NSError(domain: "错错错,说我的错,", code: 10010, userInfo: nil))
        }
        return .success
    }
    

    ok🙆‍♂️,再次搞定在这里通过链式方法调用validate验证方法,然后在闭包内部自定义验证方式,然后根据不同的状态码来做相应的自定义处理。

    retrier-重新请求

    SessionDelegate 完成请求的时候,但是请求失败的时候,会调用urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)方法,来看一下在这个方法里retrier做了什么处理

    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)
               }
           }
       }
    }
    

    这里会先判断有没有retrier,如果有就调用should方法,如果没有就直接调用完成回调。通过源码会发现retrier是继承于RequestRetrier协议的类对象(与RequestAdapter类似)同样需要自己来实现:

    extension ZHRetrier: RequestRetrier{
       func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
           completion(true,1)
           //这里不能让它一直重新请求,需要有结束方法.
           completion(false,0)
       }
    }
    

    这里should方法传入四个参数,前三个参数很简单,重点介绍⚔一下completion,completion有两个参数shouldRetry为是否请求,timeDelay为延时请求的延时时间,所以在上面的代码中写了结束再次请求的方法completion(false,0).

    let urlStr = "https://www.douban.com/j/app/radio/channels"
    let url = URL.init(string: urlStr)!
    Alamofire.SessionManager.default.retrier = ZHRetrier()
    Alamofire.request(url,method: .post,parameters: ["Username":"Henry","Age":"18"]).responseJSON {
       (response) in
       switch response.result{
       case .success(let json):
           print("json:\(json)")
           break
       case .failure(let error):
           print("error:\(error)")
           break
       }
    }.validate{ (request, response, data) -> Request.ValidationResult in
       print(response)
       guard let _ = data else {
           return .failure(NSError(domain: "你总说,是我的错", code: 10000, userInfo: nil))
       }
       let code =  response.statusCode 
       if (code == 404 ){
           return .failure(NSError(domain: "错错错,说我的错,", code: 10010, userInfo: nil))
       }
       return .success
    }
    

    同样最重要的是:Alamofire.SessionManager.default.retrier = ZHRetrier()

    Timeline-时间轴

    再次把视线拉回到文章的最开始的那副图,是不是有这句代码if startRequestsImmediately { request.resume() }
    你会发现这里是request.resume(),然而正常情况下不应该是task.resume()吗🙅‍♀️,由此可知,在这里的request.resume()方法内部必然保存了task.resume()方法。跟进去看下:


    这里resume()方法并没有传入参数,那么必然会走到else中去,delegate.queue.isSuspended = false ;如果没有任务,队列暂停挂起?

    你这怕不是在逗我,搞得我好像不太聪明的亚子??????

    有源码可知当前这个delegateTaskDelegate,进入到TaskDelegate.swift源码可以发现queueOperationQueue,并且在TaskDelegateinit方法中实现了初始化。


    可以看到queue作为 TaskDelegate 的一个属性,在初始化时成为一个同步队列,并且队列是挂起的。 也就是说在发起request之后,创建的TaskDelegate会默认初始化一个队列,并且把队列挂起。
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
            if let taskDidCompleteWithError = taskDidCompleteWithError {
                taskDidCompleteWithError(session, task, error)
            } else {
            //省略部分代码
                queue.isSuspended = false
            }
        }
    

    在这里队列就取消挂起了,这也就说明了加入到这个队列中的任务都是在请求完成之后的。

    OK🙆,下面带着这个queue来看一下Timeline的具体实现:

    1.startTime-网络请求发起时间


    resume方法中,会给 startTime 赋值当前时间戳,这就是网络请求的发起时间

    2.endTime-网络请求结束时间


    DataRequestinit方法中,会向 queue 队列中添加一个任务,获取当前的时间戳赋值给 endTime,这就是网络请求结束时间。

    但是因为此时当前队列默认为挂起状态,所以不会执行里面的任务。在网络请求完成回调 didCompleteWithError 方法时会恢复 queue队列queue.isSuspended = false,然后紧接着完成endTime赋值。

    3.initialResponseTime-初始化响应时间


    在网络请求开始返回数据时,会设置 initialResponseTime 为当前时间戳,这个时间就是初始化响应的时间。

    4.TimeLine-时间轴设置


    ResponseSerialization.swiftResponse 方法中,会向 queue队列中添加一个任务,因为当前未使用自定义的序列化方法,所以直接返回请求回来的数据,而返回的数据中保存着self.timeline.

    所以在赋值 self.timeline 时,会初始化 Timeline 对象,对前面的时间做个记录,并将当前时间戳作为参数 serializationCompletedTime的值传递给 Timeline 对象。
    然而这个 serializationCompletedTime 就是序列化结束的时间,同时这个任务也是在队列恢复时执行。

    5.初始化记录时间以及计算总时间-totalDuration


    在时间轴TimeLine的初始化方法中,记录了请求过程中的操作时间点,并计算了每个操作的时间间隔,在请求结束后返回至ResponseSerializationresponse方法中。可以看到整个时间轴TimeLine上的操作都是通过同步队列来保证的,同时也确保了操作时间的准确性。

    借用Bo_Bo大佬的总结图😀😺😁:

    总结

    关于RequestAdapter(适配器),validate(自定义验证),retrier(重新请求),Timeline(时间轴)内容就学习到这里了,个人感觉还是比较重要的,为用户的封装使用提供了一定的便利性。

    相关文章

      网友评论

          本文标题:Alamofire学习 -- Request补充

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