虽然 RxSwift 提供了丰富的处理错误操作符,如 retry
retryWhen
catchError
,但做一些定制化的处理机制直接使用这几个操作符可能是不够的,本文介绍了如何定制一些更友好的处理方案,比如,在网络错误时,由用户决定是否需要重新请求。
阅读本文前建议先阅读 RxSwift - 为什么存在 catchError。
直接使用 retry
通常不是我们想要的,我们可不想在手机没有网络情况下,一直尝试网络请求是不够友好的。
针对上述情况,我们可能采取下面两种方案解决:
这里我们来用 retryWhen
完成第二种方案。
为了更好的展示 retry 的效果,本次 demo 引入了框架 [SwiftRandom][https://github.com/thellimist/SwiftRandom]。
/// 自定义的错误
///
/// - notPositive: 不是正数
/// - oversize: 数字过大
enum MyError: Swift.Error {
case notPositive(value: Int)
case oversize(value: Int)
}
Observable<Int>
.deferred { () -> Observable<Int> in
return Observable.just(Int.random(within: -100...200))
}
.map { value -> Int in
if value <= 0 {
throw MyError.notPositive(value: value)
} else if value > 100 {
throw MyError.oversize(value: value)
} else {
return value
}
}
上述代码中我们使用 deferred
确保每次订阅都是一个随机值。
当小于 0 时,抛出一个小于 0 的错误;当大于 100 时,抛出一个数值过大的错误。
在后面接入我们的 retryWhen
方法:
.retryWhen { [unowned self] (errorObservable: Observable<MyError>) -> Observable<()> in
errorObservable
.flatMap { error -> Observable<()> in
switch error {
case let .notPositive(value):
return showAlert(title: "遇到了一个错误,是否重试?", message: "错误信息\(value) 小于 0", for: self)
.map { isEnsure in
if isEnsure {
return ()
} else {
throw error
}
}
case .oversize:
return Observable.error(error)
}
}
}
这次采取的方案是:
你可以看到我在这里指明了具体类型 (errorObservable: Observable<MyError>)
,如果遇到的是其他 Error 类型,我们将直接忽略掉。
为了获取所有的 Error ,你可以使用 Observable<Swift.Error>
。
catchError
使用 catchError
也能完成类似的需求。
上层事件传递和上一小结完全一样,为了处理通用 Error ,我添加了 LocalizedError
:
/// 自定义的错误
///
/// - notPositive: 不是正数
/// - oversize: 数字过大
enum MyError: Swift.Error, LocalizedError {
case notPositive(value: Int)
case oversize(value: Int)
var errorDescription: String? {
switch self {
case let .notPositive(value):
return "\(value)不是正数"
case let .oversize(value):
return "\(value)过大"
}
}
}
Observable<Int>
.deferred { () -> Observable<Int> in
return Observable.just(Int.random(within: -100...200))
}
.map { value -> Int in
if value <= 0 {
throw MyError.notPositive(value: value)
} else if value > 100 {
throw MyError.oversize(value: value)
} else {
return value
}
}
我们在后面接上 catchError
和 retry
:
.catchError { (error) -> Observable<Int> in
return Observable.create { [unowned self] observer in
let alert = UIAlertController(title: "遇到了一个错误,重试还是使用默认值 1 替换?", message: "错误信息:\(error.localizedDescription)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "重试", style: .cancel, handler: { _ in
observer.on(.error(error))
}))
alert.addAction(UIAlertAction(title: "替换", style: .default, handler: { _ in
observer.on(.next(1))
observer.on(.completed)
}))
self.present(alert, animated: true, completion: nil)
return Disposables.create {
alert.dismiss(animated: true, completion: nil)
}
}
}
.retry()
我们在 catchError
中返回了一个弹窗 Observable ,在重试的逻辑中向下发射了 Error ,替换的逻辑中向下发射了 1 。
并在后面接上了一个 retry
。
当选择重试的时候, retry
会接到这个错误,并重新订阅。这里需要注意的是,将 retry
直接接到弹窗O bservable 后面是错误的,此时将重新订阅弹窗 Observable ,永远触发不到最上层的随机数 Observable ,你将永远收到同一个错误。
此处错误代码示例如下:
.catchError { (error) -> Observable<Int> in
return Observable.create { [unowned self] observer in
let alert = UIAlertController(title: "遇到了一个错误,重试还是使用默认值 1 替换?", message: "错误信息:\(error.localizedDescription)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "重试", style: .cancel, handler: { _ in
observer.on(.error(error))
}))
alert.addAction(UIAlertAction(title: "替换", style: .default, handler: { _ in
observer.on(.next(1))
observer.on(.completed)
}))
self.present(alert, animated: true, completion: nil)
return Disposables.create {
alert.dismiss(animated: true, completion: nil)
}
}
.retry()
}
这里我还实现了一个二进制指数退避算法的错误处理,代码如下:
extension ObservableType {
public func retryWithBinaryExponentialBackoff(maxAttemptCount: Int, interval: TimeInterval) -> Observable<Self.E> {
return self.asObservable()
.retryWhen { (errorObservable: Observable<Swift.Error>) -> Observable<()> in
errorObservable
.scan((currentCount: 0, error: Optional<Swift.Error>.none), accumulator: { a, error in
return (currentCount: a.currentCount + 1, error: error)
})
.flatMap({ (currentCount, error) -> Observable<()> in
return ((currentCount > maxAttemptCount) ? Observable.error(error!) : Observable.just(()))
.delay(pow(2, Double(currentCount)) * interval, scheduler: MainScheduler.instance)
})
}
}
}
RxSwift 为我们提供了基本的错误处理方法,我们还可以进行一定的组合得到我们特定的错误处理方案。
在使用处理各种错误时,但需要明确的是,我们想要 retry 或许 catch 哪个 Observable 的错误。