本篇主要帶來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
/// 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) 簡書-----博客園