Alamofire源碼解讀系列(九)之響應封裝(Response)

本篇主要帶來Alamofire中Response的解讀html

前言

在每篇文章的前言部分,我都會把我認爲的本篇最重要的內容提早講一下。我更想同你們分享這些頂級框架在設計和編碼層次究竟有哪些過人的地方?固然,這些理解也都是基於我本身的理解。不免具備侷限性。swift

當咱們設計完一個Request的時候,咱們確定要處理服務器返回的響應數據。在Alamofire源碼解讀系列(一)之概述和使用中,咱們已經講過,Alamofire中把Request分爲了4類:api

  • 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

那麼,若是咱們使用下邊的代碼獲取響應數據:app

// 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) 簡書-----博客園

相關文章
相關標籤/搜索