Alamofire学习 -- Request补充
前言
通过上一篇内容学习了关于Request的基本内容,SessionManager
管理Request
和SessionDelegate
的创建,并通过task
绑定Request
;Request管理请求的参数的配置编码,创建task
和TaskDelegate
方法,然后SessionDelegate
通过task
将任务分发给 TaskDelegate
,TaskDelegate
代理执行任务的具体内容。下面对于不够完善的地方再来做一丢丢补充🧠。
Adapter-适配器
让我们把视线再拉回到上一篇中的SessionManager.swift
的request
方法:
来看👀,这里在创建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 ;
如果没有任务,队列暂停挂起?
你这怕不是在逗我,搞得我好像不太聪明的亚子??????
有源码可知当前这个delegate
是TaskDelegate
,进入到TaskDelegate.swift
源码可以发现queue
是OperationQueue
,并且在TaskDelegate
的init
方法中实现了初始化。
可以看到
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-网络请求结束时间
在
DataRequest
的init
方法中,会向 queue
队列中添加一个任务,获取当前的时间戳赋值给 endTime
,这就是网络请求结束时间。
但是因为此时当前队列默认为挂起状态,所以不会执行里面的任务。在网络请求完成回调 didCompleteWithError
方法时会恢复 queue
队列queue.isSuspended = false
,然后紧接着完成endTime
赋值。
3.initialResponseTime-初始化响应时间
在网络请求开始返回数据时,会设置
initialResponseTime
为当前时间戳,这个时间就是初始化响应的时间。
4.TimeLine-时间轴设置
在
ResponseSerialization.swift
的Response
方法中,会向 queue
队列中添加一个任务,因为当前未使用自定义的序列化方法,所以直接返回请求回来的数据,而返回的数据中保存着self.timeline
.所以在赋值
self.timeline
时,会初始化 Timeline
对象,对前面的时间做个记录,并将当前时间戳作为参数 serializationCompletedTime
的值传递给 Timeline
对象。然而这个
serializationCompletedTime
就是序列化结束的时间,同时这个任务也是在队列恢复时执行。
5.初始化记录时间以及计算总时间-totalDuration
在时间轴
TimeLine
的初始化方法中,记录了请求过程中的操作时间点,并计算了每个操作的时间间隔,在请求结束后返回至ResponseSerialization
的response
方法中。可以看到整个时间轴TimeLine
上的操作都是通过同步队列来保证的,同时也确保了操作时间的准确性。
借用Bo_Bo大佬的总结图😀😺😁:
总结
关于Request
的Adapter
(适配器),validate
(自定义验证),retrier
(重新请求),Timeline
(时间轴)内容就学习到这里了,个人感觉还是比较重要的,为用户的封装使用提供了一定的便利性。