本文上半部分將爲您解釋爲何在實際項目中爲何不要調用 onError
以及儘可能不使用 Driver
。同時給出一種合理的解決方案,讓咱們仍然能夠愉快的傳遞 Error ,並對 Value 進行處理。
下半部分將介紹用函數式來精簡咱們的代碼。ios
注:本文基於 Swift 3 。git
onError
onError
釋放資源可能這個標題有些吼人,不是說 Rx 中的 Error 處理是很好的方案嗎?能夠把 Error 歸到一塊兒處理。筆者在這裏也以爲這是一個很好的方案,但有一個地方很是頭疼,發射 Error 後是釋放對應的事件鏈,也就是數據流。仍是用網絡請求舉例,好比登陸。咱們打算作一個登陸 -》 成功 -》保存 token -》 用 token 獲取用戶信息等等。
在登陸的部分,點擊登陸,進行驗證,很明顯,若是密碼有誤,
畫圖,github
圖片表達的很清晰,對應代碼的代碼是:編程
button .rx_tap // 點擊登陸 .flatMap(provider.login) // 登陸請求 .map(saveToken) // 保存 token .flatMap(provider.requestInfo) // 獲取用戶信息 .subscribe(handleResult) // 處理結果
代碼和流程圖是一個樣子的,效果還不錯。
運行一下,輸入正確的帳號密碼,登陸,登陸成功,獲取用戶信息。一切正常。
可是咱們來看下面這種場景,登陸失敗(不管是由於網絡錯誤,仍是由於密碼錯誤之類的緣由,咱們都對這些錯誤調用了 onError
傳遞錯誤信息),直接將 error 傳遞到事件結尾,即顯示登陸錯誤的信息。此時再去點擊登陸就不會有任何提示了。
由於上面這一條點擊登陸事件鏈都被 dispose 了。json
這是一個 bug 。咱們不但願在第一次點擊登陸失敗後,再次點擊登陸缺沒什麼反應。swift
事實上在 Try! Swift 大會上有一場 POP 的分享,Demo 地址 RxPagination 。試着把網絡關了,拉取一下數據,再打開網絡,再拉取一下數據看看?此時是沒有什麼反應的。補一句,這個項目是值得學習一下的。api
在討論用官方的方式處理 Error 前,咱們先來確認一件事情,處理一個登陸流程,若是出現了錯誤是否應該繼續下去,答案是顯然的,不繼續,中止本次事件。網絡
官方給出了一下幾種操做:閉包
retry
ide
catchError
catchErrorJustReturn
doOnError
很惋惜,前三種方法都是處理 error ,將 error 變換成正常的值繼續下去,並無中止本次事件。而 doOnError
只是在出現 error 的時候作點什麼事情,並不能對事件流有什麼影響。
enum Result<T> { case value(T) case error(ErrorProtocol) }
Swift 中的枚舉關聯值是如此的強大,這能夠幫咱們解決 Error 的處理,若是 case 爲 error ,那就不處理,將 error 傳遞下去便可。
相比原有的 onError
有以下優點:
不由於 error 釋放資源
方便對 error 傳遞、處理
相似這樣:
provider.request(GitHubAPI.Authorize) .map { result -> Result<String> in switch result { case .value(let value): return Result.value(value["token"].stringValue) case .error(let error): return Result.error(error) } } .flatMap { result -> Observable<Result<JSON>> in switch result { case .value(let token): return provider.request(GitHubAPI.AccessToken(code: token)) case .error(let error): return Observable.just(Result.error(error)) } } .subscribeNext { json in // ... }
catch 等系列方法也能夠直接在這裏替代,並且更靈活了一些,能夠返回任何咱們想要的類型。
好比咱們要進行多個操做,在第一個或第二個操做就可能出現 error 時,咱們的代碼會變得很臃腫,也就是有不少的 case .error(let error):
的代碼。
這並不優雅。
在上一篇 在實踐中應用 RxSwift 1 - 使用 Result 傳遞值中,咱們解決了 error 的處理,但當咱們處理一段很長的事件流時,會發現有不少不重要的代碼,好比傳遞 Error 。本文將討論一種優雅的方式處理該問題 - 函數式。本文結構分爲兩部分,第一部分討論上一篇的 error 問題,第二部分再寫一些其它的小函數,方便咱們更好的寫代碼。
注:
本文不會爲您解釋過多關於函數式的內容,若是您須要瞭解,能夠閱讀 Chris 的 Functional Swift ,本書還有對應的中文版 函數式 Swift 。
enum Result<Value> { case value(Value) case error(ErrorProtocol) }
在上一節,咱們用 Result
解決了 onError
的問題, 但缺帶來了不少重複處理 Error
的代碼。先來嘗試下 Monad
的方案。先來寫個 map
。
func map<T>(_ transform: (Value) throws -> T) -> Result<T> { switch self { case .value(let object): do { let nextObject = try transform(object) return Result<T>.value(nextObject) } catch { return Result<T>.error(error) } case .error(let error): return Result<T>.error(error) } }
能夠看到咱們這個 map
的實現仍是很完善的:
支持對 value
的變換
支持拋出 error
筆者認爲這基本知足了咱們的需求,傳遞 Error ,對 value 進行變換,拋出錯誤。如今咱們能夠把上一篇的代碼改爲下面這個樣子:
provider .request(GitHubAPI.Authorize) .map { result in result.map { json in return json["token"].stringValue } } .flatMap { result -> Observable<Result<JSON>> in switch result { case .value(let token): return provider.request(GitHubAPI.AccessToken(code: token)) case .error(let error): return Observable.just(Result.error(error)) } } .subscribeNext { json in // ... }
易讀性仍然不夠,咱們繼續。
在 Rx 中,map
和 flatMap
是最經常使用的,咱們添加一些小工具。
func mapValue<T, K>(_ transform: (T) throws -> K) -> (Result<T>) -> Result<K> { return { result in result.map(transform) } }
因而咱們對 Result
的 map
操做能夠變成這個樣子:
.map(mapValue { json in return json["token"].stringValue }
優雅了不少,不須要再處理 error 問題了。
相似的,咱們還能夠對網絡請求的 flatMap
下手。
func flatMapRequest<T>(_ transform: (T) -> Target) -> (Result<T>) -> Observable<Result<JSON>> { return { result in let api = result.map(transform) switch api { case .value(let value): return provider.request(value) case .error(let error): return Observable.just(Result.error(error)) } } }
完整的調用就變成了這個樣子:
provider .request(GitHubAPI.Authorize) .map(mapValue { json in return json["token"].stringValue }) .flatMap(flatMapRequest { token in return GitHubAPI.AccessToken(code: token) }) .subscribeNext { result in // ... }
注:
這裏的 flatMapRequest 的 flatMap 並不是真正的 flatMap ,筆者只是方便對應 Rx 中的 flatMap 操做。以此表示這個方法是用在 flatMap 上的。
相似上面的方式,咱們還能夠寫一些經常使用的方法:
func toTrue<T>() -> T -> Bool { return { _ in return true } }
再好比調用 rx_sendMessage
時,咱們可能不須要參數:
func toVoid<T>() -> T -> Void { return { _ in } }
只須要 Result
的 value
狀況?同時獲取 value
?
func filterValue<T>() -> Result<T> -> Observable<T> { return { result in switch result { case .value(let object): return Observable.just(object) case error(let error): return Observable.empty() } } }
再好比只處理成功的狀況:
func success<T>(_ action: (T) -> Void) -> Result<T> -> Void { return { result in result.success(action) } }
甚至是帶有默認錯誤處理方法的函數,固然這裏筆者就再也不贅述,有興趣能夠自行試試看~
能夠看到,在實現每個操做符(好比 map
)中傳入的閉包,嘗試這樣函數式的代碼,會減小寫不少重複代碼,重要的是,代碼變得更加清晰易讀了。此外,您還能夠這樣組織代碼:
class FlatMap<T> { private init() { } static func request(api: (T) -> Target) -> (T) -> Observable<Result<JSON>> { return { object in return provider.request(api(object)) } } static func request(api: (T) -> Target) -> (Result<T>) -> Observable<Result<JSON>> { return { result in switch result { case .value(let object): return request(api: api)(object) case let .error(error): return Observable.just(Result.error(error)) } } } /// 過濾出 Result 中的 value static var filterValue: (Result<T>) -> Observable<T> { return { result in switch result { case .value(let object): return Observable.just(object) case .error: return Observable.empty() } } } /// 過濾出 Result 中的 error static var filterError: (Result<T>) -> Observable<ErrorProtocol> { return { result in switch result { case .value: return Observable.empty() case let .error(error): return Observable.just(error) } } } }
這裏我表示全部的方法都是用給 Rx 中 flatMap
操做的。
關於爲何
FlatMap
中會有filter
,您能夠參考這篇文章 用更 Swifty 的代碼遍歷數據 。這裏還有一篇美團的 FRP 實踐 iOS開發下的函數響應式編程 ,不論您是用 RAC 仍是 Rx ,都值得看一看。