Alamofire源码解读系列(九)之响应封装(Response)

本篇主要带来Alamofire中Response的解读

前言

在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下。我更想同大家分享这些*框架在设计和编码层次究竟有哪些过人的地方?当然,这些理解也都是基于我自己的理解。难免具有局限性。

当我们设计完一个Request的时候,我们肯定要处理服务器返回的响应数据。在Alamofire源码解读系列(一)之概述和使用中,我们已经讲过,Alamofire中把Request分为了4类:

  • DataRequest
  • DownloadRequest
  • UploadRequest
  • StreamRequest

Alamofire中Request可以使用链式访问:

Alamofire.request("https://httpbin.org/get")
.responseString { response in
print("Response String: \(response.result.value)")
}
.responseJSON { response in
print("Response JSON: \(response.result.value)")
}

能实现链式访问的原理就是每个函数的返回值都是Self。那么在上边的代码中,虽然response的名字都一样,但并不是同一类型。

因为有4中不同的Request类型,StreamRequest我们先不提,对于UploadRequest来说,服务器响应的数据比较简单,就响应一个结果就行,因此不需要对它的Response专门进行封装。因此,Alamofire设计了2中与之相对应的Response类型,他们分别是:

  • DefaultDataResponse / DataResponse
  • DefaultDownloadResponse / DataResponse

那么,如果我们使用下边的代码获取响应数据:

// Response Handler - Unserialized Response
func response(
queue: DispatchQueue?,
completionHandler: @escaping (DefaultDataResponse) -> Void)
-> Self

获取的是没有经过序列化后的数据,如果使用了没有序列化的response方法,返回的就是带有Default开头的响应者,比如DefaultDataResponse,DefaultDownloadResponse。如果使用了序列化的response方法,返回的就是DataResponse或者DataResponse。

这说明了什么? 在程序的设计层面上,这种前后呼应的手法能够让代码更好理解。就像在项目中不能把所有的参数都放到一个模型中一样。

拿DefaultDataResponse / DataResponse来举例,DataResponse基本上只比DefaultDataResponse多了一个系列化后的数据属性。

还有一点要提一下,先假设每个Request都可以被序列化为JSON,或者String。这些都属于序列化Response的范围。序列化成功后,保存数据的容器应该有一个类型,而这个类型又是可以变化的,在本篇文章下边的内容中会指出泛型的使用方法。

DefaultDataResponse

DefaultDataResponse用于存储data或upload请求情况下的所有无序列化的数据。那么在Alamofire中对于服务器的响应主要关心的数据有下边几个:

  • request: URLRequest? 表示该响应来源于那个请求
  • response: HTTPURLResponse? 服务器返回的响应
  • data: Data? 响应数据
  • error: Error? 在请求中可能发生的错误
  • timeline: Timeline 请求的时间线封装,这个会在后续的文章中解释
  • _metrics: AnyObject? 包含了请求和响应的统计信息

代码如下:

 /// The URL request sent to the server.
public let request: URLRequest? /// The server's response to the URL request.
public let response: HTTPURLResponse? /// The data returned by the server.
public let data: Data? /// The error encountered while executing or validating the request.
public let error: Error? /// The timeline of the complete lifecycle of the request.
public let timeline: Timeline var _metrics: AnyObject?

一般来说,在swift中,如果只是为了保存数据,那么应该把这个类设计成struct。struct是 值传递,因此对数据的操作更安全。除了定义需要保存的数据属性后,必须设计一个符合要求的构造函数。

 /// Creates a `DefaultDataResponse` instance from the specified parameters.
///
/// - Parameters:
/// - request: The URL request sent to the server.
/// - response: The server's response to the URL request.
/// - data: The data returned by the server.
/// - error: The error encountered while executing or validating the request.
/// - timeline: The timeline of the complete lifecycle of the request. `Timeline()` by default.
/// - metrics: The task metrics containing the request / response statistics. `nil` by default.
public init(
request: URLRequest?,
response: HTTPURLResponse?,
data: Data?,
error: Error?,
timeline: Timeline = Timeline(),
metrics: AnyObject? = nil)
{
self.request = request
self.response = response
self.data = data
self.error = error
self.timeline = timeline
}

DataResponse

DataResponse比上边的DefaultDataResponse多了一个result属性,该属性存储了序列化后的数据。它的类型是 Result,关于Result的详情内容,请看这篇文章Alamofire源码解读系列(五)之结果封装(Result)

/// Used to store all data associated with a serialized response of a data or upload request.
public struct DataResponse<Value> {
/// The URL request sent to the server.
public let request: URLRequest? /// The server's response to the URL request.
public let response: HTTPURLResponse? /// The data returned by the server.
public let data: Data? /// The result of response serialization.
public let result: Result<Value> /// The timeline of the complete lifecycle of the request.
public let timeline: Timeline /// Returns the associated value of the result if it is a success, `nil` otherwise.
public var value: Value? { return result.value } /// Returns the associated error value if the result if it is a failure, `nil` otherwise.
public var error: Error? { return result.error } var _metrics: AnyObject? /// Creates a `DataResponse` instance with the specified parameters derived from response serialization.
///
/// - parameter request: The URL request sent to the server.
/// - parameter response: The server's response to the URL request.
/// - parameter data: The data returned by the server.
/// - parameter result: The result of response serialization.
/// - parameter timeline: The timeline of the complete lifecycle of the `Request`. Defaults to `Timeline()`.
///
/// - returns: The new `DataResponse` instance.
public init(
request: URLRequest?,
response: HTTPURLResponse?,
data: Data?,
result: Result<Value>,
timeline: Timeline = Timeline())
{
self.request = request
self.response = response
self.data = data
self.result = result
self.timeline = timeline
}
}

DataResponse: CustomStringConvertible, CustomDebugStringConvertible

DataResponse实现了CustomStringConvertible和CustomDebugStringConvertible协议,因此可以自定义DataResponse的打印信息。

我们也可以给我们模型实现这两个协议,在代码调试的时候,打印出详细的信息,比打断点来查看效率更高。

extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible {
/// The textual representation used when written to an output stream, which includes whether the result was a
/// success or failure.
public var description: String {
return result.debugDescription
} /// The debug textual representation used when written to an output stream, which includes the URL request, the URL
/// response, the server data, the response serialization result and the timeline.
public var debugDescription: String {
var output: [String] = [] output.append(request != nil ? "[Request]: \(request!.httpMethod ?? "GET") \(request!)" : "[Request]: nil")
output.append(response != nil ? "[Response]: \(response!)" : "[Response]: nil")
output.append("[Data]: \(data?.count ?? 0) bytes")
output.append("[Result]: \(result.debugDescription)")
output.append("[Timeline]: \(timeline.debugDescription)") return output.joined(separator: "\n")
}
}

DefaultDownloadResponse

DefaultDownloadResponse保存的是下载任务的数据。有3个属性需要做一下介绍:

  • temporaryURL: URL? 现在成功后,数据会被保存在这个临时URL中
  • destinationURL: URL? 目标URL,如果设置了该属性,那么文件会复制到该URL中
  • resumeData: Data? 表示可恢复的数据,对于下载任务,如果因为某种原因下载中断了,或失败了,可以使用该数据恢复之前的下载

其他的内容跟上边介绍的内容没什么特别的地方,就简单的把代码弄上来了:

/// Used to store all data associated with an non-serialized response of a download request.
public struct DefaultDownloadResponse {
/// The URL request sent to the server.
public let request: URLRequest? /// The server's response to the URL request.
public let response: HTTPURLResponse? /// The temporary destination URL of the data returned from the server.
public let temporaryURL: URL? /// The final destination URL of the data returned from the server if it was moved.
public let destinationURL: URL? /// The resume data generated if the request was cancelled.
public let resumeData: Data? /// The error encountered while executing or validating the request.
public let error: Error? /// The timeline of the complete lifecycle of the request.
public let timeline: Timeline var _metrics: AnyObject? /// Creates a `DefaultDownloadResponse` instance from the specified parameters.
///
/// - Parameters:
/// - request: The URL request sent to the server.
/// - response: The server's response to the URL request.
/// - temporaryURL: The temporary destination URL of the data returned from the server.
/// - destinationURL: The final destination URL of the data returned from the server if it was moved.
/// - resumeData: The resume data generated if the request was cancelled.
/// - error: The error encountered while executing or validating the request.
/// - timeline: The timeline of the complete lifecycle of the request. `Timeline()` by default.
/// - metrics: The task metrics containing the request / response statistics. `nil` by default.
public init(
request: URLRequest?,
response: HTTPURLResponse?,
temporaryURL: URL?,
destinationURL: URL?,
resumeData: Data?,
error: Error?,
timeline: Timeline = Timeline(),
metrics: AnyObject? = nil)
{
self.request = request
self.response = response
self.temporaryURL = temporaryURL
self.destinationURL = destinationURL
self.resumeData = resumeData
self.error = error
self.timeline = timeline
}
}

DownloadResponse

这个也没什么好说的,直接上代码:

/// Used to store all data associated with a serialized response of a download request.
public struct DownloadResponse<Value> {
/// The URL request sent to the server.
public let request: URLRequest? /// The server's response to the URL request.
public let response: HTTPURLResponse? /// The temporary destination URL of the data returned from the server.
public let temporaryURL: URL? /// The final destination URL of the data returned from the server if it was moved.
public let destinationURL: URL? /// The resume data generated if the request was cancelled.
public let resumeData: Data? /// The result of response serialization.
public let result: Result<Value> /// The timeline of the complete lifecycle of the request.
public let timeline: Timeline /// Returns the associated value of the result if it is a success, `nil` otherwise.
public var value: Value? { return result.value } /// Returns the associated error value if the result if it is a failure, `nil` otherwise.
public var error: Error? { return result.error } var _metrics: AnyObject? /// Creates a `DownloadResponse` instance with the specified parameters derived from response serialization.
///
/// - parameter request: The URL request sent to the server.
/// - parameter response: The server's response to the URL request.
/// - parameter temporaryURL: The temporary destination URL of the data returned from the server.
/// - parameter destinationURL: The final destination URL of the data returned from the server if it was moved.
/// - parameter resumeData: The resume data generated if the request was cancelled.
/// - parameter result: The result of response serialization.
/// - parameter timeline: The timeline of the complete lifecycle of the `Request`. Defaults to `Timeline()`.
///
/// - returns: The new `DownloadResponse` instance.
public init(
request: URLRequest?,
response: HTTPURLResponse?,
temporaryURL: URL?,
destinationURL: URL?,
resumeData: Data?,
result: Result<Value>,
timeline: Timeline = Timeline())
{
self.request = request
self.response = response
self.temporaryURL = temporaryURL
self.destinationURL = destinationURL
self.resumeData = resumeData
self.result = result
self.timeline = timeline
}
}

DownloadResponse: CustomStringConvertible, CustomDebugStringConvertible

extension DownloadResponse: CustomStringConvertible, CustomDebugStringConvertible {
/// The textual representation used when written to an output stream, which includes whether the result was a
/// success or failure.
public var description: String {
return result.debugDescription
} /// The debug textual representation used when written to an output stream, which includes the URL request, the URL
/// response, the temporary and destination URLs, the resume data, the response serialization result and the
/// timeline.
public var debugDescription: String {
var output: [String] = [] output.append(request != nil ? "[Request]: \(request!.httpMethod ?? "GET") \(request!)" : "[Request]: nil")
output.append(response != nil ? "[Response]: \(response!)" : "[Response]: nil")
output.append("[TemporaryURL]: \(temporaryURL?.path ?? "nil")")
output.append("[DestinationURL]: \(destinationURL?.path ?? "nil")")
output.append("[ResumeData]: \(resumeData?.count ?? 0) bytes")
output.append("[Result]: \(result.debugDescription)")
output.append("[Timeline]: \(timeline.debugDescription)") return output.joined(separator: "\n")
}
}

protocol Response

protocol Response {
/// The task metrics containing the request / response statistics.
var _metrics: AnyObject? { get set }
mutating func add(_ metrics: AnyObject?)
} extension Response {
mutating func add(_ metrics: AnyObject?) {
#if !os(watchOS)
guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) else { return }
guard let metrics = metrics as? URLSessionTaskMetrics else { return } _metrics = metrics
#endif
}
} // MARK: - @available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension DefaultDataResponse: Response {
#if !os(watchOS)
/// The task metrics containing the request / response statistics.
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
#endif
} @available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension DataResponse: Response {
#if !os(watchOS)
/// The task metrics containing the request / response statistics.
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
#endif
} @available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension DefaultDownloadResponse: Response {
#if !os(watchOS)
/// The task metrics containing the request / response statistics.
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
#endif
} @available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension DownloadResponse: Response {
#if !os(watchOS)
/// The task metrics containing the request / response statistics.
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
#endif
}

上边的协议中有一个属性和一个方法,如果在协议中实现了自身的方法,那么实现该协议的对象可以不用实现该协议中的方法。在上边介绍的属性中 _metrics是来自该协议的属性。在上边的初始化方法中也没有_metrics这一项

在swift中,当多个对象公用一个属性或者方法时,就可以考虑协议了。

在这里按照上边的用法,举个简单的例子。

public struct Person {
public var name: String
public var age: UInt
var _hobby: String? init(name: String, age: UInt) {
self.name = name
self.age = age
}
} var person = Person(name: "James", age: 30)
print(person.name) person.name = "Bond"
print(person.name) var person1 = person
print(person1.name) person1.name = "Rose"
print(person1.name)
print(person.name) protocol Hobbyable {
var _hobby: String? { get set }
mutating func addHobby(_ hobby: String?)
} extension Hobbyable {
mutating func addHobby(_ hobby: String?) {
_hobby = hobby
}
} extension Person: Hobbyable {
var hobby: String? {
return _hobby
}
} person1.addHobby("Books")
print(person1.hobby ?? "")

总结

由于知识水平有限,如有错误,还望指出

链接

Alamofire源码解读系列(一)之概述和使用 简书-----博客园

Alamofire源码解读系列(二)之错误处理(AFError) 简书-----博客园

Alamofire源码解读系列(三)之通知处理(Notification) 简书-----博客园

Alamofire源码解读系列(四)之参数编码(ParameterEncoding) 简书-----博客园

Alamofire源码解读系列(五)之结果封装(Result) 简书-----博客园

Alamofire源码解读系列(六)之Task代理(TaskDelegate) 简书-----博客园

Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager) 简书-----博客园

Alamofire源码解读系列(八)之安全策略(ServerTrustPolicy) 简书-----博客园

上一篇:Spark RDD/Core 编程 API入门系列 之rdd案例(map、filter、flatMap、groupByKey、reduceByKey、join、cogroupy等)(四)


下一篇:Alamofire源码解读系列(十)之序列化(ResponseSerialization)