Error Handling

一旦序列里面產(chǎn)出了一個(gè) error 事件,整個(gè)序列將被終止。RxSwift 主要有兩種錯(cuò)誤處理機(jī)制:

  • retry - 重試
  • catch - 恢復(fù)

retry

retry 可以讓序列在發(fā)生錯(cuò)誤后重試:

// 請(qǐng)求 JSON 失敗時(shí)弊攘,立即重試锌钮,
// 重試 3 次后仍然失敗,就將錯(cuò)誤拋出

let rxJson: Observable<JSON> = ...

rxJson
    .retry(3)
    .subscribe(onNext: { json in
        print("取得 JSON 成功: \(json)")
    }, onError: { error in
        print("取得 JSON 失敗: \(error)")
    })
    .disposed(by: disposeBag)

retryWhen

如果我們需要在發(fā)生錯(cuò)誤時(shí)楞捂,經(jīng)過一段延時(shí)后重試薄坏,那可以這樣實(shí)現(xiàn):

// 請(qǐng)求 JSON 失敗時(shí),等待 5 秒后重試寨闹,

let retryDelay: Double = 5  // 重試延時(shí) 5 秒

rxJson
    .retryWhen { (rxError: Observable<Error>) -> Observable<Int> in
        return Observable.timer(retryDelay, scheduler: MainScheduler.instance)
    }
    .subscribe(...)
    .disposed(by: disposeBag)

這里我們需要用到 retryWhen 操作符胶坠,這個(gè)操作符主要描述應(yīng)該在何時(shí)重試,并且通過閉包里面返回的 Observable 來控制重試的時(shí)機(jī):

.retryWhen { (rxError: Observable<Error>) -> Observable<Int> in
    ...
}

閉包里面的參數(shù)是 Observable<Error> 也就是所產(chǎn)生錯(cuò)誤的序列鼻忠,然后返回值是一個(gè) Observable涵但。當(dāng)這個(gè)返回的 Observable 發(fā)出一個(gè)元素時(shí),就進(jìn)行重試操作帖蔓。當(dāng)它發(fā)出一個(gè) error 或者 completed 事件時(shí)矮瘟,就不會(huì)重試,并且將這個(gè)事件傳遞給到后面的觀察者塑娇。

如果需要加上一個(gè)最大重試次數(shù)的限制:

// 請(qǐng)求 JSON 失敗時(shí)澈侠,等待 5 秒后重試,
// 重試 4 次后仍然失敗埋酬,就將錯(cuò)誤拋出

let maxRetryCount = 4       // 最多重試 4 次
let retryDelay: Double = 5  // 重試延時(shí) 5 秒

rxJson
    .retryWhen { (rxError: Observable<Error>) -> Observable<Int> in
        return rxError.flatMapWithIndex { (error, index) -> Observable<Int> in
            guard index < maxRetryCount else {
                return Observable.error(error)
            }
            return Observable<Int>.timer(retryDelay, scheduler: MainScheduler.instance)
        }
    }
    .subscribe(...)
    .disposed(by: disposeBag)

我們這里要實(shí)現(xiàn)的是哨啃,如果重試超過 4 次,就將錯(cuò)誤拋出写妥。如果錯(cuò)誤在 4 次以內(nèi)時(shí)拳球,就等待 5 秒后重試:

rxError.flatMapWithIndex { (error, index) -> Observable<Int> in
    guard index < maxRetryCount else {
        return Observable.error(error)
    }
    return Observable<Int>.timer(retryDelay, scheduler: MainScheduler.instance)
}

我們用 flatMapWithIndex 這個(gè)操作符,因?yàn)樗梢越o我們提供錯(cuò)誤的索引數(shù) index珍特。然后用這個(gè)索引數(shù)判斷是否超過最大重試數(shù)祝峻,如果超過了,就將錯(cuò)誤拋出扎筒。如果沒有超過莱找,就等待 5 秒后重試。

catchError

catchError 可以在錯(cuò)誤產(chǎn)生時(shí)嗜桌,用一個(gè)備用元素或者一組備用元素將錯(cuò)誤替換掉:

searchBar.rx.text.orEmpty
    ...
    .flatMapLatest { query -> Observable<[Repository]> in
        ...
        return searchGitHub(query)
            .catchErrorJustReturn([])
    }
    ...
    .bind(to: ...)
    .disposed(by: disposeBag)

你也可以使用 catchError奥溺,當(dāng)錯(cuò)誤產(chǎn)生時(shí),將錯(cuò)誤事件替換成一個(gè)備選序列:

// 先從網(wǎng)絡(luò)獲取數(shù)據(jù)骨宠,如果獲取失敗了浮定,就從本地緩存獲取數(shù)據(jù)

let rxData: Observable<Data> = ...      // 網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)
let cahcedData: Observable<Data> = ...  // 之前本地緩存的數(shù)據(jù)

rxData
    .catchError { _ in cahcedData }
    .subscribe(onNext: { date in
        print("獲取數(shù)據(jù)成功: \(date.count)")
    })
    .disposed(by: disposeBag)

Result

如果我們只是想給用戶錯(cuò)誤提示,那要如何操作呢层亿?

以下提供一個(gè)最為直接的方案壶唤,不過這個(gè)方案存在一些問題:

// 當(dāng)用戶點(diǎn)擊更新按鈕時(shí),
// 就立即取出修改后的用戶信息棕所。
// 然后發(fā)起網(wǎng)絡(luò)請(qǐng)求闸盔,進(jìn)行更新操作,
// 一旦操作失敗就提示用戶失敗原因

updateUserInfoButton.rx.tap
    .withLatestFrom(rxUserInfo)
    .flatMapLatest { userInfo -> Observable<Void> in
        return update(userInfo)
    }
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: {
        print("用戶信息更新成功")
    }, onError: { error in
        print("用戶信息更新失斄帐 : \(error.localizedDescription)")
    })
    .disposed(by: disposeBag)

這樣實(shí)現(xiàn)是非常直接的迎吵。但是一旦網(wǎng)絡(luò)請(qǐng)求操作失敗了躲撰,序列就會(huì)終止。整個(gè)訂閱將被取消击费。如果用戶再次點(diǎn)擊更新按鈕拢蛋,就無法再次發(fā)起網(wǎng)絡(luò)請(qǐng)求進(jìn)行更新操作了。

為了解決這個(gè)問題蔫巩,我們需要選擇合適的方案來進(jìn)行錯(cuò)誤處理谆棱。例如使用枚舉 Result:

// 自定義一個(gè)枚舉類型 Result
public enum Result<T> {
    case success(T)
    case failure(Swift.Error)
}

然后之前的代碼需要修改成:

updateUserInfoButton.rx.tap
    .withLatestFrom(rxUserInfo)
    .flatMapLatest { userInfo -> Observable<Result<Void>> in
        return update(userInfo)
            .map(Result.success)  // 轉(zhuǎn)換成 Result
            .catchError { error in Observable.just(Result.failure(error)) }
    }
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { result in
        switch result {           // 處理 Result
        case .success:
            print("用戶信息更新成功")
        case .failure(let error):
            print("用戶信息更新失敗: \(error.localizedDescription)")
        }
    })
    .disposed(by: disposeBag)

這樣我們的錯(cuò)誤事件被包裝成了 Result.failure(Error) 元素圆仔,就不會(huì)終止整個(gè)序列垃瞧。就算網(wǎng)絡(luò)請(qǐng)求失敗,整個(gè)訂閱依然存在坪郭。如果用戶再次點(diǎn)擊更新按鈕个从,也是能夠發(fā)起網(wǎng)絡(luò)請(qǐng)求進(jìn)行更新操作的。

另外你也可以使用 materialize 操作符來進(jìn)行錯(cuò)誤處理歪沃。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嗦锐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子沪曙,更是在濱河造成了極大的恐慌奕污,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件液走,死亡現(xiàn)場(chǎng)離奇詭異碳默,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)育灸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門腻窒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昵宇,“玉大人磅崭,你說我怎么就攤上這事⊥甙ィ” “怎么了砸喻?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蒋譬。 經(jīng)常有香客問我割岛,道長(zhǎng),這世上最難降的妖魔是什么犯助? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任癣漆,我火速辦了婚禮,結(jié)果婚禮上剂买,老公的妹妹穿的比我還像新娘惠爽。我一直安慰自己癌蓖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布婚肆。 她就那樣靜靜地躺著租副,像睡著了一般。 火紅的嫁衣襯著肌膚如雪较性。 梳的紋絲不亂的頭發(fā)上用僧,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音赞咙,去河邊找鬼责循。 笑死,一個(gè)胖子當(dāng)著我的面吹牛人弓,可吹牛的內(nèi)容都是我干的沼死。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼崔赌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼意蛀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起健芭,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤县钥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后慈迈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體若贮,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年痒留,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谴麦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡伸头,死狀恐怖匾效,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情恤磷,我是刑警寧澤面哼,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站扫步,受9級(jí)特大地震影響魔策,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜河胎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一闯袒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦政敢、人聲如沸原茅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擂橘。三九已至,卻和暖如春摩骨,著一層夾襖步出監(jiān)牢的瞬間通贞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工恼五, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昌罩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓灾馒,卻偏偏與公主長(zhǎng)得像茎用,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子睬罗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容