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

本篇主要帶來(lái)Alamofire中Response的解讀

前言

在每篇文章的前言部分,我都會(huì)把我認(rèn)為的本篇最重要的內(nèi)容提前講一下。我更想同大家分享這些頂級(jí)框架在設(shè)計(jì)和編碼層次究竟有哪些過(guò)人的地方?當(dāng)然,這些理解也都是基于我自己的理解。難免具有局限性。

當(dāng)我們?cè)O(shè)計(jì)完一個(gè)Request的時(shí)候,我們肯定要處理服務(wù)器返回的響應(yīng)數(shù)據(jù)。在Alamofire源碼解讀系列(一)之概述和使用中,我們已經(jīng)講過(guò),Alamofire中把Request分為了4類(lèi):

  • DataRequest
  • DownloadRequest
  • UploadRequest
  • StreamRequest

Alamofire中Request可以使用鏈?zhǔn)皆L(fǎng)問(wèn):

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

能實(shí)現(xiàn)鏈?zhǔn)皆L(fǎng)問(wèn)的原理就是每個(gè)函數(shù)的返回值都是Self。那么在上邊的代碼中,雖然response的名字都一樣,但并不是同一類(lèi)型。

因?yàn)橛?中不同的Request類(lèi)型,StreamRequest我們先不提,對(duì)于UploadRequest來(lái)說(shuō),服務(wù)器響應(yīng)的數(shù)據(jù)比較簡(jiǎn)單,就響應(yīng)一個(gè)結(jié)果就行,因此不需要對(duì)它的Response專(zhuān)門(mén)進(jìn)行封裝。因此,Alamofire設(shè)計(jì)了2中與之相對(duì)應(yīng)的Response類(lèi)型,他們分別是:

  • DefaultDataResponse / DataResponse
  • DefaultDownloadResponse / DataResponse

那么,如果我們使用下邊的代碼獲取響應(yīng)數(shù)據(jù):

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

獲取的是沒(méi)有經(jīng)過(guò)序列化后的數(shù)據(jù),如果使用了沒(méi)有序列化的response方法,返回的就是帶有Default開(kāi)頭的響應(yīng)者,比如DefaultDataResponse,DefaultDownloadResponse。如果使用了序列化的response方法,返回的就是DataResponse或者DataResponse。

這說(shuō)明了什么? 在程序的設(shè)計(jì)層面上,這種前后呼應(yīng)的手法能夠讓代碼更好理解。就像在項(xiàng)目中不能把所有的參數(shù)都放到一個(gè)模型中一樣。

拿DefaultDataResponse / DataResponse來(lái)舉例,DataResponse基本上只比DefaultDataResponse多了一個(gè)系列化后的數(shù)據(jù)屬性。

還有一點(diǎn)要提一下,先假設(shè)每個(gè)Request都可以被序列化為JSON,或者String。這些都屬于序列化Response的范圍。序列化成功后,保存數(shù)據(jù)的容器應(yīng)該有一個(gè)類(lèi)型,而這個(gè)類(lèi)型又是可以變化的,在本篇文章下邊的內(nèi)容中會(huì)指出泛型的使用方法。

DefaultDataResponse

DefaultDataResponse用于存儲(chǔ)data或upload請(qǐng)求情況下的所有無(wú)序列化的數(shù)據(jù)。那么在Alamofire中對(duì)于服務(wù)器的響應(yīng)主要關(guān)心的數(shù)據(jù)有下邊幾個(gè):

  • request: URLRequest? 表示該響應(yīng)來(lái)源于那個(gè)請(qǐng)求
  • response: HTTPURLResponse? 服務(wù)器返回的響應(yīng)
  • data: Data? 響應(yīng)數(shù)據(jù)
  • error: Error? 在請(qǐng)求中可能發(fā)生的錯(cuò)誤
  • timeline: Timeline 請(qǐng)求的時(shí)間線(xiàn)封裝,這個(gè)會(huì)在后續(xù)的文章中解釋
  • _metrics: AnyObject? 包含了請(qǐng)求和響應(yīng)的統(tǒng)計(jì)信息

代碼如下:

 /// 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?

一般來(lái)說(shuō),在swift中,如果只是為了保存數(shù)據(jù),那么應(yīng)該把這個(gè)類(lèi)設(shè)計(jì)成struct。struct是 值傳遞,因此對(duì)數(shù)據(jù)的操作更安全。除了定義需要保存的數(shù)據(jù)屬性后,必須設(shè)計(jì)一個(gè)符合要求的構(gòu)造函數(shù)。

 /// 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<Value>

DataResponse<Value>比上邊的DefaultDataResponse多了一個(gè)result屬性,該屬性存儲(chǔ)了序列化后的數(shù)據(jù)。它的類(lèi)型是 Result<Value>,關(guān)于Result的詳情內(nèi)容,請(qǐng)看這篇文章Alamofire源碼解讀系列(五)之結(jié)果封裝(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實(shí)現(xiàn)了CustomStringConvertible和CustomDebugStringConvertible協(xié)議,因此可以自定義DataResponse的打印信息。

我們也可以給我們模型實(shí)現(xiàn)這兩個(gè)協(xié)議,在代碼調(diào)試的時(shí)候,打印出詳細(xì)的信息,比打斷點(diǎn)來(lái)查看效率更高。

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保存的是下載任務(wù)的數(shù)據(jù)。有3個(gè)屬性需要做一下介紹:

  • temporaryURL: URL? 現(xiàn)在成功后,數(shù)據(jù)會(huì)被保存在這個(gè)臨時(shí)URL中
  • destinationURL: URL? 目標(biāo)URL,如果設(shè)置了該屬性,那么文件會(huì)復(fù)制到該URL中
  • resumeData: Data? 表示可恢復(fù)的數(shù)據(jù),對(duì)于下載任務(wù),如果因?yàn)槟撤N原因下載中斷了,或失敗了,可以使用該數(shù)據(jù)恢復(fù)之前的下載

其他的內(nèi)容跟上邊介紹的內(nèi)容沒(méi)什么特別的地方,就簡(jiǎn)單的把代碼弄上來(lái)了:

/// 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

這個(gè)也沒(méi)什么好說(shuō)的,直接上代碼:

/// 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
}

上邊的協(xié)議中有一個(gè)屬性和一個(gè)方法,如果在協(xié)議中實(shí)現(xiàn)了自身的方法,那么實(shí)現(xiàn)該協(xié)議的對(duì)象可以不用實(shí)現(xiàn)該協(xié)議中的方法。在上邊介紹的屬性中 _metrics是來(lái)自該協(xié)議的屬性。在上邊的初始化方法中也沒(méi)有_metrics這一項(xiàng)

在swift中,當(dāng)多個(gè)對(duì)象公用一個(gè)屬性或者方法時(shí),就可以考慮協(xié)議了。

在這里按照上邊的用法,舉個(gè)簡(jiǎn)單的例子。

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 ?? "")

總結(jié)

由于知識(shí)水平有限,如有錯(cuò)誤,還望指出

鏈接

Alamofire源碼解讀系列(一)之概述和使用 簡(jiǎn)書(shū)-----博客園

Alamofire源碼解讀系列(二)之錯(cuò)誤處理(AFError) 簡(jiǎn)書(shū)-----博客園

Alamofire源碼解讀系列(三)之通知處理(Notification) 簡(jiǎn)書(shū)-----博客園

Alamofire源碼解讀系列(四)之參數(shù)編碼(ParameterEncoding) 簡(jiǎn)書(shū)-----博客園

Alamofire源碼解讀系列(五)之結(jié)果封裝(Result) 簡(jiǎn)書(shū)-----博客園

Alamofire源碼解讀系列(六)之Task代理(TaskDelegate) 簡(jiǎn)書(shū)-----博客園

Alamofire源碼解讀系列(七)之網(wǎng)絡(luò)監(jiān)控(NetworkReachabilityManager) 簡(jiǎn)書(shū)-----博客園

Alamofire源碼解讀系列(八)之安全策略(ServerTrustPolicy) 簡(jiǎn)書(shū)-----博客園

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容