本篇講解Result的封裝html
有時候,咱們會根據現實中的事物來對程序中的某個業務關係進行抽象,這句話很難理解。在Alamofire中,使用Response
來描述請求後的結果。咱們都知道Alamofire返回的數據能夠通過特殊的處理,好比說序列化,那麼咱們應該如何在Response
中獲取到這些類型不一樣的數據呢?git
假如說序列化後的數據是data,最直接的想法就是把data設置爲Any類型,在實際用到的時候在進行判斷,這也是最普通的一種開發思惟。如今咱們就要打破這種思惟。咱們須要封裝一個對象,這個對象可以表達任何結果,這就用到了swift中的泛型。github
接下來在講解Result
以後,會給出兩個使用泛型的例子,第一個例子表達基本的網絡封裝思想,第二個表達基本的viewModel思想。編程
/// 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
你們注意,泛型的寫法是相似這樣的:
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
那麼問題來了,不是把數據解析成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實現了CustomStringConvertible
和CustomDebugStringConvertible
協議 :
// 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 的邂逅
咱們須要一個協議,這個協議提供一個函數,目的是把Data轉換成實現該協議的對象自己。注意咱們在這時候是不知道這個對象的類型的,爲了適配更多的類型,這個對象暫時設計爲泛型,所以協議中的函數應該是靜態函數
protocol Decodable { static func parse(data: Data) -> Self? }
封裝請求,一樣採用協議的方式
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 }
封裝發送端,一樣採用協議的方式
protocol Client { var host: String { get } func send<T: Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void); }
只要是實現了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) } } } }
封裝完成以後,咱們來使用一下上邊封裝的功能:
建立一個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 }
建立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 } } }
發送請求
let request = TestRequest(name: "mama", userId: "12345"); AlamofireClient.default.send(request) { (response, error) in print(response) }
對網絡的基本封裝就到此爲止了 ,這裏的Result能夠是任何類型的對象,好比說User,能夠經過上邊的方法,直接解析成User對象。
這種設計一般應用在MVVM之中,咱們看下邊的代碼:
定義一個協議,這個協議提供一個函數,函數會提供一個參數,這個參數就是viewModel。cell只要實現了這個協議,就可以經過這個參數拿到viewModel,而後根據viewModel來配置自身控件的屬性。
protocol Updatable: class { associatedtype ViewData func update(viewData: ViewData) }
再定義一個協議,這個協議須要表示cell的一些信息,好比reuseIdentifier,cellClass,同時,這個協議還須要提供一個方法,賦予cell適配器更新cell的能力
protocol CellConfiguratorType { var reuseIdentifier: String { get } var cellClass: AnyClass { get } func update(cell: UITableViewCell) }
建立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類型的數據。
看看使用示例:
建立數組
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")), ])
註冊cell
func registerCells() { for cellConfigurator in items { tableView.register(cellConfigurator.cellClass, forCellReuseIdentifier: cellConfigurator.reuseIdentifier) } }
配置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) 簡書博客園