本篇講解Result的封裝
前言
有時(shí)候,我們會(huì)根據(jù)現(xiàn)實(shí)中的事物來對(duì)程序中的某個(gè)業(yè)務(wù)關(guān)系進(jìn)行抽象胜茧,這句話很難理解垢揩。在Alamofire中闹获,使用Response
來描述請(qǐng)求后的結(jié)果。我們都知道Alamofire返回的數(shù)據(jù)可以經(jīng)過特殊的處理颜懊,比如說序列化财岔,那么我們應(yīng)該如何在Response
中獲取到這些類型不同的數(shù)據(jù)呢?
假如說序列化后的數(shù)據(jù)是data河爹,最直接的想法就是把data設(shè)置為Any類型匠璧,在實(shí)際用到的時(shí)候在進(jìn)行判斷,這也是最普通的一種開發(fā)思維∠陶猓現(xiàn)在我們就要打破這種思維夷恍。我們需要封裝一個(gè)對(duì)象,這個(gè)對(duì)象能夠表達(dá)任何結(jié)果媳维,這就用到了swift中的泛型酿雪。
接下來在講解Result
之后,會(huì)給出兩個(gè)使用泛型的例子侄刽,第一個(gè)例子表達(dá)基本的網(wǎng)絡(luò)封裝思想指黎,第二個(gè)表達(dá)基本的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)
}
關(guān)于如何描述結(jié)果
,有兩種可能州丹,不是成功就是失敗醋安,因此考慮使用枚舉杂彭。在Alamofire源碼解讀系列(二)之錯(cuò)誤處理(AFError)這篇文章中我已經(jīng)詳細(xì)的講解了枚舉的使用方法。在上邊的代碼中吓揪,對(duì)枚舉的每個(gè)子選項(xiàng)都做了值關(guān)聯(lián)亲怠。
大家注意,泛型的寫法是類似這樣的:<T>柠辞,在<和>之間聲明一種類型团秽,這個(gè)T知識(shí)象征性的,在賦值的時(shí)候钾腺,可以是任何類型徙垫。還有一種用法,看下邊的代碼:
struct CellConfigurator<Cell> where Cell: Updatable, Cell: UITableViewCell {
}
上邊代碼中的Cell必須符合后邊給出的兩個(gè)條件才行放棒,這種用法是給泛型增加了條件限制姻报,這種用法還有另外一種方式,看下邊的代碼:
func send<T: Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void);
其實(shí)道理都差不多间螟,都屬于對(duì)泛型的靈活運(yùn)用吴旋。
我們接著看看在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
)
}
上邊的這個(gè)函數(shù)的主要目的是把請(qǐng)求成功后的結(jié)果序列化為JSON,completionHandler函數(shù)的參數(shù)類型為DataResponse<Any>厢破,其中的Any就會(huì)傳遞給Result荣瑟,也就是Result<Any>。
那么問題來了摩泪,不是把數(shù)據(jù)解析成JSON了嗎笆焰?為什么要返回Any類型呢?json本質(zhì)上很類似于JavaScript中的對(duì)象和數(shù)組见坑。JSONSerialization.jsonObject返回的類型是Any嚷掠,這是因?yàn)榻馕龊蟮臄?shù)據(jù)有可能是數(shù)組,也有可能是字典荞驴。
字典:
{
"people":[
{"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"},
{"firstName":"Jason","lastName":"Hunter","email":"bbbb"},
{"firstName":"Elliotte","lastName":"Harold","email":"cccc"}
]
}
數(shù)組:
[
"a",
"b",
"c"
]
當(dāng)然如果不是這兩種格式的數(shù)據(jù)不皆,使用JSONSerialization.jsonObject解析會(huì)拋出異常。
到這里我們就大概對(duì)這個(gè)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
}
}
當(dāng)然,為了打印更加詳細(xì)的信息鲫骗,使Result實(shí)現(xiàn)了CustomStringConvertible
和CustomDebugStringConvertible
協(xié)議 :
// 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是一個(gè)比較簡(jiǎn)單的封裝。
基于泛型的網(wǎng)絡(luò)封裝
在實(shí)際的開發(fā)工作中挎峦,我們使用Alamofire發(fā)送請(qǐng)求香追,獲取服務(wù)器的數(shù)據(jù),往往會(huì)對(duì)其進(jìn)行二次封裝坦胶,在這里透典,我講解一個(gè)封裝的例子晴楔,內(nèi)容來自面向協(xié)議編程與 Cocoa 的邂逅
-
我們需要一個(gè)協(xié)議,這個(gè)協(xié)議提供一個(gè)函數(shù)峭咒,目的是把Data轉(zhuǎn)換成實(shí)現(xiàn)該協(xié)議的對(duì)象本身税弃。注意我們?cè)谶@時(shí)候是不知道這個(gè)對(duì)象的類型的,為了適配更多的類型凑队,這個(gè)對(duì)象暫時(shí)設(shè)計(jì)為泛型则果,因此協(xié)議中的函數(shù)應(yīng)該是靜態(tài)函數(shù)
protocol Decodable { static func parse(data: Data) -> Self? }
-
封裝請(qǐng)求,同樣采用協(xié)議的方式
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 }
-
封裝發(fā)送端漩氨,同樣采用協(xié)議的方式
protocol Client { var host: String { get } func send<T: Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void); }
-
只要是實(shí)現(xiàn)了Client協(xié)議的對(duì)象西壮,就有能力發(fā)送請(qǐng)求,在這里Alamofire是作為中間層存在的叫惊,只提供請(qǐng)求能力款青,可以隨意換成其他的中間能力層
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) } } } }
封裝完成之后,我們來使用一下上邊封裝的功能:
-
創(chuàng)建一個(gè)TestRequest.swift文件霍狰,內(nèi)部代碼為:
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 }
-
創(chuàng)建TestResult.swift文件抡草,內(nèi)部代碼為:
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 } } }
-
發(fā)送請(qǐng)求
let request = TestRequest(name: "mama", userId: "12345"); AlamofireClient.default.send(request) { (response, error) in print(response) }
對(duì)網(wǎng)絡(luò)的基本封裝就到此為止了 ,這里的Result可以是任何類型的對(duì)象蔗坯,比如說User康震,可以通過上邊的方法,直接解析成User對(duì)象宾濒。
基于泛型的cell封裝
這種設(shè)計(jì)通常應(yīng)用在MVVM之中腿短,我們看下邊的代碼:
-
定義一個(gè)協(xié)議,這個(gè)協(xié)議提供一個(gè)函數(shù)绘梦,函數(shù)會(huì)提供一個(gè)參數(shù)答姥,這個(gè)參數(shù)就是viewModel。cell只要實(shí)現(xiàn)了這個(gè)協(xié)議谚咬,就能夠通過這個(gè)參數(shù)拿到viewModel,然后根據(jù)viewModel來配置自身控件的屬性尚粘。
protocol Updatable: class { associatedtype ViewData func update(viewData: ViewData) }
-
再定義一個(gè)協(xié)議择卦,這個(gè)協(xié)議需要表示cell的一些信息,比如reuseIdentifier郎嫁,cellClass秉继,同時(shí),這個(gè)協(xié)議還需要提供一個(gè)方法泽铛,賦予cell適配器更新cell的能力
protocol CellConfiguratorType { var reuseIdentifier: String { get } var cellClass: AnyClass { get } func update(cell: UITableViewCell) }
-
創(chuàng)建CellConfigurator尚辑,這個(gè)CellConfigurator必須綁定一個(gè)viewData,這個(gè)viewData通過Updatable協(xié)議中的方法傳遞給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) } } }
萬變不離其宗啊盔腔,我們?cè)谡?qǐng)求到數(shù)據(jù)之后杠茬,需要把數(shù)據(jù)轉(zhuǎn)變成CellConfigurator月褥,也就是在數(shù)組中存放的是CellConfigurator類型的數(shù)據(jù)。
看看使用示例:
-
創(chuàng)建數(shù)組
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")), ])
-
注冊(cè)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 }
這個(gè)cell封裝思想出自這里https://github.com/fastred/ConfigurableTableViewController
總結(jié)
上邊兩個(gè)例子瓢喉,我解釋的并不是很詳細(xì)宁赤,只需要打開源碼,仔細(xì)琢磨琢磨就能體會(huì)到里邊的妙處栓票,如有問題决左,可以留言。
在這里獲取代碼:https://github.com/agelessman/TTestDemo
由于知識(shí)水平有限走贪,如有錯(cuò)誤佛猛,還望指出
鏈接
Alamofire源碼解讀系列(一)之概述和使用 簡(jiǎn)書博客園
Alamofire源碼解讀系列(二)之錯(cuò)誤處理(AFError) 簡(jiǎn)書博客園