本篇讲解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) 简书博客园