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) 简书-----博客园
Alamofire源码解读系列(九)之响应封装(Response)的更多相关文章
- Alamofire源码解读系列(五)之结果封装(Result)
本篇讲解Result的封装 前言 有时候,我们会根据现实中的事物来对程序中的某个业务关系进行抽象,这句话很难理解.在Alamofire中,使用Response来描述请求后的结果.我们都知道Alamof ...
- Alamofire源码解读系列(十)之序列化(ResponseSerialization)
本篇主要讲解Alamofire中如何把服务器返回的数据序列化 前言 和前边的文章不同, 在这一篇中,我想从程序的设计层次上解读ResponseSerialization这个文件.更直观的去探讨该功能是 ...
- Alamofire源码解读系列(十一)之多表单(MultipartFormData)
本篇讲解跟上传数据相关的多表单 前言 我相信应该有不少的开发者不明白多表单是怎么一回事,然而事实上,多表单确实很简单.试想一下,如果有多个不同类型的文件(png/txt/mp3/pdf等等)需要上传给 ...
- Alamofire源码解读系列(十二)之时间轴(Timeline)
本篇带来Alamofire中关于Timeline的一些思路 前言 Timeline翻译后的意思是时间轴,可以表示一个事件从开始到结束的时间节点.时间轴的概念能够应用在很多地方,比如说微博的主页就是一个 ...
- Alamofire源码解读系列(十二)之请求(Request)
本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...
- Alamofire源码解读系列(六)之Task代理(TaskDelegate)
本篇介绍Task代理(TaskDelegate.swift) 前言 我相信可能有80%的同学使用AFNetworking或者Alamofire处理网络事件,并且这两个框架都提供了丰富的功能,我也相信很 ...
- Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager)
Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager) 本篇主要讲解iOS开发中的网络监控 前言 在开发中,有时候我们需要获取这些信息: 手机是否联网 ...
- Alamofire源码解读系列(八)之安全策略(ServerTrustPolicy)
本篇主要讲解Alamofire中安全验证代码 前言 作为开发人员,理解HTTPS的原理和应用算是一项基本技能.HTTPS目前来说是非常安全的,但仍然有大量的公司还在使用HTTP.其实HTTPS也并不是 ...
- Alamofire源码解读系列(二)之错误处理(AFError)
本篇主要讲解Alamofire中错误的处理机制 前言 在开发中,往往最容易被忽略的内容就是对错误的处理.有经验的开发者,能够对自己写的每行代码负责,而且非常清楚自己写的代码在什么时候会出现异常,这样就 ...
随机推荐
- React-intl 实现多语言
前言 React 做国际化,我推荐使用 React-intl , 这个库提供了 React 组件和Api两种方式来格式化日期,数字和字符串等.知道这个库了,那让我们开始使用它 组件用法 为了和Reac ...
- Java变量&&简单程序流程&&循环
变量:强类型局部变量: 1.先赋值,后使用 2.作用范围:从定义开始,到所在代码块结束 3.重合范围内不允许重复命名 数据类型(8中基本类型) byte 1B -128~127 short 2B -3 ...
- californium 框架设计分析
Californium 源码分析 1. Californium 项目简介 Californium 是一款基于Java实现的Coap技术框架,该项目实现了Coap协议的各种请求响应定义,支持CON/NO ...
- 读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问
1.为什么需要访问资源管理类中的原生资源 资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不 ...
- iphone在iframe页面的宽度不受父页面影响,避免撑开页面
工作中有个需求,就是产品页面通过iframe引用显示产品协议页,要求不要横向滑动,只需要竖向滑动,但在iphone中引用的iframe会撑开父页的宽度,而在android端浏览器这不会. <di ...
- node将excel内容转json
小颖分享的这个方法,前提是你已经安装了node,如果大家不知道自己是否安装过node可以打开cmd,然后执行:node -v,如果安装过,你会看到你安装的node版本号,如果没有安装请先安装node. ...
- [看图说话] 基于Spark UI性能优化与调试——初级篇
Spark有几种部署的模式,单机版.集群版等等,平时单机版在数据量不大的时候可以跟传统的java程序一样进行断电调试.但是在集群上调试就比较麻烦了...远程断点不太方便,只能通过Log的形式,进行分析 ...
- 【PHP系列】PHP组件详解
缘起 枫爷之前做过几年的PHP的研发,大部分都是在开源框架的引导下,编写代码.现在依然,本能的会去让我使用某个PHP框架开发PHP应用,也是因为懒吧,没有好好的去研究研究除了框架之外的一些东西. 今天 ...
- Javascript学习九
计时器setInterval() 在执行时,从载入页面后每隔指定的时间执行代码. 语法: setInterval(代码,交互时间); 参数说明: 1. 代码:要调用的函数或要执行的代码串. 2. 交互 ...
- LOGISTIC回归分析
前面的博客有介绍过对连续的变量进行线性回归分析,从而达到对因变量的预测或者解释作用.那么如果因变量是离散变量呢?在做行为预测的时候通常只有"做"与"不做的区别" ...