在實踐中應用 RxSwift

摘要

本文上半部分將爲您解釋爲何在實際項目中爲何不要調用 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 ?

在討論用官方的方式處理 Error 前,咱們先來確認一件事情,處理一個登陸流程,若是出現了錯誤是否應該繼續下去,答案是顯然的,不繼續,中止本次事件。網絡

官方給出了一下幾種操做:閉包

  • retryide

  • catchError

  • catchErrorJustReturn

  • doOnError

很惋惜,前三種方法都是處理 error ,將 error 變換成正常的值繼續下去,並無中止本次事件。而 doOnError 只是在出現 error 的時候作點什麼事情,並不能對事件流有什麼影響。

使用 Result

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 添加 map 和 flatMap

在上一節,咱們用 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 中,mapflatMap 是最經常使用的,咱們添加一些小工具。

mapValue

func mapValue<T, K>(_ transform: (T) throws -> K) -> (Result<T>) -> Result<K> {
    return { result in
        result.map(transform)
    }
}

因而咱們對 Resultmap 操做能夠變成這個樣子:

.map(mapValue { json in
        return json["token"].stringValue
    }

優雅了不少,不須要再處理 error 問題了。

flatMapRequest

相似的,咱們還能夠對網絡請求的 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 }
}

只須要 Resultvalue 狀況?同時獲取 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 ,都值得看一看。

相關文章
相關標籤/搜索