Alamofire源碼解讀系列(五)之結果封裝(Result)

本篇講解Result的封裝html

前言

有時候,咱們會根據現實中的事物來對程序中的某個業務關係進行抽象,這句話很難理解。在Alamofire中,使用Response來描述請求後的結果。咱們都知道Alamofire返回的數據能夠通過特殊的處理,好比說序列化,那麼咱們應該如何在Response中獲取到這些類型不一樣的數據呢?git

假如說序列化後的數據是data,最直接的想法就是把data設置爲Any類型,在實際用到的時候在進行判斷,這也是最普通的一種開發思惟。如今咱們就要打破這種思惟。咱們須要封裝一個對象,這個對象可以表達任何結果,這就用到了swift中的泛型。github

接下來在講解Result以後,會給出兩個使用泛型的例子,第一個例子表達基本的網絡封裝思想,第二個表達基本的viewModel思想。編程

Result

/// Used to represent whether a request was successful or encountered an error.
///
/// - success: The request and all post processing operations were successful resulting in the serialization of the
///            provided associated value.
///
/// - failure: The request encountered an error resulting in a failure. The associated values are the original data
///            provided by the server as well as the error that caused the failure.
public enum Result<Value> {
    case success(Value)
    case failure(Error)

}

關於如何描述結果,有兩種可能,不是成功就是失敗,所以考慮使用枚舉。在Alamofire源碼解讀系列(二)之錯誤處理(AFError)這篇文章中我已經詳細的講解了枚舉的使用方法。在上邊的代碼中,對枚舉的每一個子選項都作了值關聯。json

你們注意,泛型的寫法是相似這樣的: ,在 <和> 之間聲明一種類型,這個T知識象徵性的,在賦值的時候,能夠是任何類型。還有一種用法,看下邊的代碼: swift

struct CellConfigurator<Cell> where Cell: Updatable, Cell: UITableViewCell {
}

上邊代碼中的Cell必須符合後邊給出的兩個條件才行,這種用法是給泛型增長了條件限制,這種用法還有另一種方式,看下邊的代碼:api

func send<T: Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void);

其實道理都差很少,都屬於對泛型的靈活運用。數組

咱們接着看看在Alamofire中是如何使用Result的。服務器

@discardableResult
    public func responseJSON(
        queue: DispatchQueue? = nil,
        options: JSONSerialization.ReadingOptions = .allowFragments,
        completionHandler: @escaping (DataResponse<Any>) -> Void)
        -> Self
    {
        return response(
            queue: queue,
            responseSerializer: DataRequest.jsonResponseSerializer(options: options),
            completionHandler: completionHandler
        )
    }

上邊的這個函數的主要目的是把請求成功後的結果序列化爲JSON,completionHandler函數的參數類型爲DataResponse ,其中的Any就會傳遞給Result,也就是Result 網絡

那麼問題來了,不是把數據解析成JSON了嗎?爲何要返回Any類型呢?json本質上很相似於JavaScript中的對象和數組。JSONSerialization.jsonObject返回的類型是Any,這是由於解析後的數據有多是數組,也有多是字典。

字典:

{
    "people":[
        {"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"},
        {"firstName":"Jason","lastName":"Hunter","email":"bbbb"},
        {"firstName":"Elliotte","lastName":"Harold","email":"cccc"}
    ]
}

數組:

[
    "a",
    "b",
    "c"
]

固然若是不是這兩種格式的數據,使用JSONSerialization.jsonObject解析會拋出異常。

到這裏咱們就大概對這個Result有了必定的瞭解,下邊的代碼給result添加了一些屬性,主要目的是使用起來更方便:

/// Returns `true` if the result is a success, `false` otherwise.
    public var isSuccess: Bool {
        switch self {
        case .success:
            return true
        case .failure:
            return false
        }
    }

    /// Returns `true` if the result is a failure, `false` otherwise.
    public var isFailure: Bool {
        return !isSuccess
    }

    /// Returns the associated value if the result is a success, `nil` otherwise.
    public var value: Value? {
        switch self {
        case .success(let value):
            return value
        case .failure:
            return nil
        }
    }

    /// Returns the associated error value if the result is a failure, `nil` otherwise.
    public var error: Error? {
        switch self {
        case .success:
            return nil
        case .failure(let error):
            return error
        }
    }

固然,爲了打印更加詳細的信息,使Result實現了CustomStringConvertibleCustomDebugStringConvertible協議 :

// MARK: - CustomStringConvertible

extension Result: CustomStringConvertible {
    /// The textual representation used when written to an output stream, which includes whether the result was a
    /// success or failure.
    public var description: String {
        switch self {
        case .success:
            return "SUCCESS"
        case .failure:
            return "FAILURE"
        }
    }
}

// MARK: - CustomDebugStringConvertible

extension Result: CustomDebugStringConvertible {
    /// The debug textual representation used when written to an output stream, which includes whether the result was a
    /// success or failure in addition to the value or error.
    public var debugDescription: String {
        switch self {
        case .success(let value):
            return "SUCCESS: \(value)"
        case .failure(let error):
            return "FAILURE: \(error)"
        }
    }
}

總起來講,Result是一個比較簡單的封裝。

基於泛型的網絡封裝

在實際的開發工做中,咱們使用Alamofire發送請求,獲取服務器的數據,每每會對其進行二次封裝,在這裏,我講解一個封裝的例子,內容來自面向協議編程與 Cocoa 的邂逅

  1. 咱們須要一個協議,這個協議提供一個函數,目的是把Data轉換成實現該協議的對象自己。注意咱們在這時候是不知道這個對象的類型的,爲了適配更多的類型,這個對象暫時設計爲泛型,所以協議中的函數應該是靜態函數

    protocol Decodable {
         static func parse(data: Data) -> Self?
     }
  2. 封裝請求,一樣採用協議的方式

    public enum JZGHTTPMethod: String {
         case options = "OPTIONS"
         case get     = "GET"
         case head    = "HEAD"
         case post    = "POST"
         case put     = "PUT"
         case patch   = "PATCH"
         case delete  = "DELETE"
         case trace   = "TRACE"
         case connect = "CONNECT"
    
     }
    
     protocol Request {
    
         var path: String { get }
         var privateHost: String? { get }
    
         var HTTPMethod: JZGHTTPMethod { get }
         var timeoutInterval: TimeInterval { get }
         var parameter: [String: Any]? { get }
    
         associatedtype Response: Decodable
     }
  3. 封裝發送端,一樣採用協議的方式

    protocol Client {
    
         var host: String { get }
    
         func send<T: Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void);
     }
  4. 只要是實現了Client協議的對象,就有能力發送請求,在這裏Alamofire是做爲中間層存在的,只提供請求能力,能夠隨意換成其餘的中間能力層

    struct AlamofireClient: Client {
    
         public static let `default` = { AlamofireClient() }()
    
         public enum HostType: String {
             case sandbox = "https://httpbin.org/post"
         }
    
         /// Base host URL
         var host: String = HostType.sandbox.rawValue
    
         func send<T : Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void) {
    
             let url = URL(string: r.privateHost ?? host.appending(r.path))!
    
             let sessionManager = Alamofire.SessionManager.default
             sessionManager.session.configuration.timeoutIntervalForRequest = r.timeoutInterval
    
             Alamofire.request(url, method: HTTPMethod(rawValue: r.HTTPMethod.rawValue)!,
                               parameters: r.parameter,
                               encoding: URLEncoding.default,
                               headers: nil)
                 .response { (response) in
    
                     if let data = response.data, let res = T.Response.parse(data: data) {
    
                         handler(res, nil)
    
                     }else {
    
                         handler(nil, response.error?.localizedDescription)
                     }
             }
         }
    
     }

封裝完成以後,咱們來使用一下上邊封裝的功能:

  1. 建立一個TestRequest.swift文件,內部代碼爲:

    struct TestRequest: Request {
         let name: String
         let userId: String
    
         var path: String {
             return ""
         }
    
         var privateHost: String? {
             return nil
         }
    
         var timeoutInterval: TimeInterval {
             return 20.0
         }
    
         var HTTPMethod: JZGHTTPMethod {
             return .post
         }
    
         var parameter: [String : Any]? {
             return ["name" : name,
                     "userId" : userId]
         }
    
         typealias Response = TestResult
     }
  2. 建立TestResult.swift文件,內部代碼爲:

    struct TestResult {
         var origin: String
     }
    
     extension TestResult: Decodable {
         static func parse(data: Data) -> TestResult? {
             do {
                let dic = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
    
                 guard let dict = dic as? Dictionary<String, Any> else {
                     return nil
                 }
                 return TestResult(origin: dict["origin"] as! String)
             }catch {
                 return nil
             }
         }
     }
  3. 發送請求

    let request = TestRequest(name: "mama", userId: "12345");
     AlamofireClient.default.send(request) { (response, error) in
         print(response)
     }

對網絡的基本封裝就到此爲止了 ,這裏的Result能夠是任何類型的對象,好比說User,能夠經過上邊的方法,直接解析成User對象。

基於泛型的cell封裝

這種設計一般應用在MVVM之中,咱們看下邊的代碼:

  1. 定義一個協議,這個協議提供一個函數,函數會提供一個參數,這個參數就是viewModel。cell只要實現了這個協議,就可以經過這個參數拿到viewModel,而後根據viewModel來配置自身控件的屬性。

    protocol Updatable: class {
    
         associatedtype ViewData
    
         func update(viewData: ViewData)
     }
  2. 再定義一個協議,這個協議須要表示cell的一些信息,好比reuseIdentifier,cellClass,同時,這個協議還須要提供一個方法,賦予cell適配器更新cell的能力

    protocol CellConfiguratorType {
    
         var reuseIdentifier: String { get }
         var cellClass: AnyClass { get }
    
         func update(cell: UITableViewCell)
     }
  3. 建立CellConfigurator,這個CellConfigurator必須綁定一個viewData,這個viewData經過Updatable協議中的方法傳遞給cell

    struct CellConfigurator<Cell> where Cell: Updatable, Cell: UITableViewCell {
    
         let viewData: Cell.ViewData
         let reuseIdentifier: String = NSStringFromClass(Cell.self)
         let cellClass: AnyClass = Cell.self
    
         func update(cell: UITableViewCell) {
             if let cell = cell as? Cell {
                 cell.update(viewData: viewData)
             }
         }
     }

    萬變不離其宗啊,咱們在請求到數據以後,須要把數據轉變成CellConfigurator,也就是在數組中存放的是CellConfigurator類型的數據。

看看使用示例:

  1. 建立數組

    let viewController = ConfigurableTableViewController(items: [
                 CellConfigurator<TextTableViewCell>(viewData: TextCellViewData(title: "Foo")),
                 CellConfigurator<ImageTableViewCell>(viewData: ImageCellViewData(image: UIImage(named: "og")!)),
                 CellConfigurator<ImageTableViewCell>(viewData: ImageCellViewData(image: UIImage(named: "GoogleLogo")!)),
                 CellConfigurator<TextTableViewCell>(viewData: TextCellViewData(title: "Bar")),
                 ])
  2. 註冊cell

    func registerCells() {
             for cellConfigurator in items {
                 tableView.register(cellConfigurator.cellClass, forCellReuseIdentifier: cellConfigurator.reuseIdentifier)
             }
         }
  3. 配置cell

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
             let cellConfigurator = items[(indexPath as NSIndexPath).row]
             let cell = tableView.dequeueReusableCell(withIdentifier: cellConfigurator.reuseIdentifier, for: indexPath)
             cellConfigurator.update(cell: cell)
             return cell
         }

這個cell封裝思想出自這裏https://github.com/fastred/ConfigurableTableViewController

總結

上邊兩個例子,我解釋的並非很詳細,只須要打開源碼,仔細琢磨琢磨就能體會到裏邊的妙處,若有問題,能夠留言。

在這裏獲取代碼:https://github.com/agelessman/TTestDemo

因爲知識水平有限,若有錯誤,還望指出

連接

Alamofire源碼解讀系列(一)之概述和使用 簡書博客園

Alamofire源碼解讀系列(二)之錯誤處理(AFError) 簡書博客園

Alamofire源碼解讀系列(三)之通知處理(Notification) 簡書博客園

Alamofire源碼解讀系列(四)之參數編碼(ParameterEncoding) 簡書博客園

相關文章
相關標籤/搜索