阅读 46

Alamofire学习 -- Request补充

前言

通过上一篇内容学习了关于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(时间轴)内容就学习到这里了,个人感觉还是比较重要的,为用户的封装使用提供了一定的便利性。

关注下面的标签,发现更多相似文章
评论