一旦序列里面產(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ò)誤處理歪沃。