Alamofire - 你需要知道的细节
2019-08-22 本文已影响2人
Cooci_和谐学习_不急不躁
上一个篇章里面我们讲解
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实现了
CustomStringConvertible
和CustomDebugStringConvertible
协议 :
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
的其他时间就是通过已知的initialResponseTime
和requestStartTime
、requestCompletedTime
、serializationCompletedTime
计算得出!
这个篇章就先写到这里吧!一不小心又是
01:40
! 虽然这个点发出去,也不会有几个人看了🙁🙁🙁。但是我希望支持我的小伙伴👬,睡一觉醒来自然而然就能接受到来自 Cooci 给你文章推送通知!一起的一切又是那么的美好😸😸😸,今夜睡去,期待美梦之后的奋斗,加油~~~~~~💪💪💪就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!