前言
Alamofire
设计了2种与Request
相对应的Response
类型,他们分别是:
-
DefaultDataResponse / DataResponse
-- >DataRequest
,UploadRequest
-
DefaultDownloadResponse / DataResponse
-->DownloadRequest
- 如果使用了没有序列化的
response
方法,返回的就是带有Default开头的响应者,比如DefaultDataResponse
,DefaultDownloadResponse
。 - 如果使用了序列化的
response
方法,返回的就是DataResponse
或者DataResponse
。 - 拿
DefaultDataResponse
/DataResponse
来举例,DataResponse
基本上只比DefaultDataResponse
多了一个系列化后的数据属性。
Response流程分析
先看一段简单代码
SessionManager.default
.request(urlString)
.response { (response) in
print(response)
}
.responseJSON { (jsonResponse) in
print(jsonResponse)
}
- 因为
Alamofire
是采用的链式调用设计,所以可以在调用response
后还能继续调用responseJSON
。能实现链式访问的原理就是每个函数的返回值都是Self
。 - 在上面的代码中,先调用了
request
,再调用了response
。那么Alamofire
是怎么保证response
的的执行时机是在request
发起请求并在数据回调之后再执行的呢?这里一定有猫腻,要不然只要调用了response
方法就会里面执行里面的代码。进入到response
方法里看看:
public func response(queue: DispatchQueue? = nil, completionHandler: @escaping (DefaultDataResponse) -> Void) -> Self {
delegate.queue.addOperation {
(queue ?? DispatchQueue.main).async {
var dataResponse = DefaultDataResponse(
request: self.request,
response: self.response,
data: self.delegate.data,
error: self.delegate.error,
timeline: self.timeline
)
dataResponse.add(self.delegate.metrics)
completionHandler(dataResponse)
}
}
return self
}
- 把
response
加到了一个队列queue
中
self.queue = {
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
operationQueue.qualityOfService = .utility
return operationQueue
}()
-
operationQueue.maxConcurrentOperationCount = 1
说明是一个串行队列 -
operationQueue.isSuspended = true
队列默认是挂起状态 - 相信看到这里就能够猜到具体是怎么实现的了。
- 调用
response
方法时把任务加到队列queue
中 -
queue
是一个串行队列,并且默认是挂起状态,所以先不执行任务 - 在请求完成的代理回调方法中把
queue.isSuspended = false
,开始执行队列中的任务。
- 调用
- 找到请求完成的代理回调发现确实如上所说。
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let taskDidCompleteWithError = taskDidCompleteWithError {
taskDidCompleteWithError(session, task, error)
} else {
// 省略无关代码......
queue.isSuspended = false
}
}
DefaultDataResponse
把目光移到response
方法,创建了一个DefaultDataResponse
对象并作为completionHandler
的参数回调出去。
public struct DefaultDataResponse {
/// 表示该响应来源于那个请求
public let request: URLRequest?
/// 服务器返回的响应
public let response: HTTPURLResponse?
/// 响应数据
public let data: Data?
/// 在请求中可能发生的错误
public let error: Error?
/// 请求的时间线封装
public let timeline: Timeline
/// 包含了请求和响应的统计信息
var _metrics: AnyObject?
}
-
DefaultDataResponse
是一个结构体类型,用来保存数据。一般来说,在Swift
中,如果只是为了保存数据,那么应该把这个类设计成struct
。struct
是值传递,因此对数据的操作更安全。 - 把所有关于
Response
的数据全部保存在DefaultDataResponse
结构体中,化零为整,很好的一个面向对象的设计原则,把这个完整的数据返回给用户,用户想用什么就取什么。 - 我们来重点看下保存在
DefaultDataResponse
中的data
是如何来的。继续来到response
方法中:
var dataResponse = DefaultDataResponse(
request: self.request,
response: self.response,
data: self.delegate.data,
error: self.delegate.error,
timeline: self.timeline
)
- 点进去找到
DataTaskDelegate
里的data
,发现其实返回的是mutableData
,那么mutableData
又是什么呢?
override var data: Data? {
if dataStream != nil {
return nil
} else {
return mutableData
}
}
- 在当前文件中搜索mutableData发现:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
dataTaskDidReceiveData(session, dataTask, data)
} else {
if let dataStream = dataStream {
dataStream(data)
} else {
mutableData.append(data)
}
// 省略无关代码......
}
}
- 在接收数据的代理方法中,把接收到的数据拼接在
mutableData
中。
DataResponse
DataResponse
比上边的DefaultDataResponse
多了一个public let result: Result
属性,该属性存储了序列化后的数据。接着看看在Alamofire
中是如何使用Result
的,来到responseJSON
方法中:
public func responseJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (DataResponse) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.jsonResponseSerializer(options: options),
completionHandler: completionHandler
)
}
- 上边的这个函数的主要目的是把请求成功后的结果序列化为
JSON
再返回,completionHandler
函数的参数类型为DataResponse
,其中的Any
就会传递给Result
,也就是Result
。
DataResponseSerializer
一般来说,我们需要对response.data
做序列化处理之后才方便使用。Alamofire
已经提供了一些常用的序列化器,可以直接调用api使用。同样也可以自定义序列化器来实现自己功能。下面来看看responseJSON
方法时怎么实现的。
public func responseJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (DataResponse) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.jsonResponseSerializer(options: options),
completionHandler: completionHandler
)
}
- 其实内部也是调用的
response
方法,只是传了一个json
序列化器作为参数
public static func jsonResponseSerializer(
options: JSONSerialization.ReadingOptions = .allowFragments)
-> DataResponseSerializer
{
return DataResponseSerializer { _, response, data, error in
return Request.serializeResponseJSON(options: options, response: response, data: data, error: error)
}
}
- 初始化了一个
DataResponseSerializer
结构体,并且保存了一个尾随闭包serializeResponse
。 - 通过前面对
Response
流程分析可以知道,当请求完成回调之后代码会执行到response
方法中加入到队列的任务。也就会调用上面的尾随闭包。 - 点击进入到
Request.serializeResponseJSON
方法
public static func serializeResponseJSON(
options: JSONSerialization.ReadingOptions,
response: HTTPURLResponse?,
data: Data?,
error: Error?)
-> Result
{
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) }
guard let validData = data, validData.count > 0 else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
}
do {
let json = try JSONSerialization.jsonObject(with: validData, options: options)
return .success(json)
} catch {
return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)))
}
}
- 返回的是一个
Result
类型的枚举,如果有错误信息,就返回return .failure(error!)
并把错误信息做完枚举的关联值返回出去。 - 如果成功就把结果序列化之后返回出去
return .success(json)
。 - 所以可以在处理请求结果时,可以直接通过
dataResponse.result
来判断请求结果成功还是失败。
总结
- 把
response
方法的响应结果回调completionHandler(dataResponse)
加入到一个串联队列,并且这个队列是默认挂起的,当请求完成后队列开始执行,这样才能保证是在请求完成之后再回调结果。 - 可以自定义系列化器,只需要自定义的序列化器实现
DataResponseSerializerProtocol
这个协议就可以。 -
DataResponse
帮助我们统一管理请求过程中的数据,请求成功、失败、时间轴等等,便于业务层处理。
有问题或者建议和意见,欢迎大家评论或者私信。
喜欢的朋友可以点下关注和喜欢,后续会持续更新文章。