RxSwift) error handling - catch, catchAndReturn, retry, retryWhen, materialize, dematerialize
✅ error handling - catch, catchAndReturn, retry, retryWhen, materialize, dematerialize
- catch : error 를 새로운 Observable 또는 값으로 처리
- retry : 재시도
- materialize / dematerialize : sequence 를 제어해서 처리
onError 에서 처리하면 되지 않나요?
Error 이벤트는 Observable 을 종료시키게 됩니다. 그래서 종료시키지 않고 다음과 같이 이벤트를 발생키시고 completed 가 발생되도록 error handling 을 하고자 합니다.
catch
- catch
extension ObservableType {
/**
Continues an observable sequence that is terminated by an error with the observable sequence produced by the handler.
*/
public func `catch`(_ handler: @escaping (Swift.Error) throws -> Observable<Element>)
-> Observable<Element> {
Catch(source: self.asObservable(), handler: handler)
}
}
next, completed 이벤트를 방출하지만 error 이벤트가 방출되면 기존의 Observable 에서 새로운 Observable 로 바꾸어서 핸들링할 수 있습니다.
예를 들어, 서버 통신 시에 next, completed 이벤트를 방출하다가 error 이벤트가 방출되면 내가 정해둔 동작으로 대체할 수 있습니다.
let subject = PublishSubject<Int>()
let recovery = PublishSubject<Int>()
subject.catch { _ in
recovery
}
.subscribe { print($0) }
.disposed(by: disposeBag)
subject.onError(MyError.error)
subject.onNext(1) // Error 이벤트는 Observable 을 종료시키기 때문에 방출되지 않음.
recovery.onNext(2) // 새로운 Observable 이 next(2)을 방출함.
recovery.onCompleted()
- catchAndReturn
RxSwift 6.0 에서 catchErrorJustReturn
이 catchAndReturn
으로 이름이 바뀌었습니다.
https://github.com/ReactiveX/RxSwift/releases/tag/6.0.0-rc.1
extension ObservableType {
/**
Continues an observable sequence that is terminated by an error with a single element.
- seealso: [catch operator on reactivex.io](http://reactivex.io/documentation/operators/catch.html)
- parameter element: Last element in an observable sequence in case error occurs.
- returns: An observable sequence containing the source sequence's elements, followed by the `element` in case an error occurred.
*/
public func catchAndReturn(_ element: Element)
-> Observable<Element> {
Catch(source: self.asObservable(), handler: { _ in Observable.just(element) })
}
}
error 로 종료된 Observable 시퀀스가 단일 element 을 방출합니다.
error 가 발생했을 때 기본값을 방출하거나 로컬의 캐싱된 데이터를 방출할 때 사용됩니다.
error 의 종류와 상관없이 단일 element 를 방출하는 단점이 존재.
let subject = PublishSubject<Int>()
subject
.catchAndReturn(0)
.subscribe { print($0) }
.disposed(by: disposeBag)
subject.onError(MyError.error) // next(0)
retry
- retry
Observable 이 Error 이벤트를 방출하면 성공할 때까지 계속 시도하거나 최대 재시도 횟수를 지정할 수 있습니다.
extension ObservableType {
/**
Repeats the source observable sequence until it successfully terminates.
*/
public func retry() -> Observable<Element> {
CatchSequence(sources: InfiniteSequence(repeatedValue: self.asObservable()))
}
maxAttemptCount 파라미터로 최대 retry 횟수를 지정할 수 있다. 이때는 처음 시도 횟수도 포함한다.
extension ObservableType {
/**
Repeats the source observable sequence the specified number of times in case of an error or until it successfully terminates.
*/
public func retry(_ maxAttemptCount: Int)
-> Observable<Element> {
CatchSequence(sources: Swift.repeatElement(self.asObservable(), count: maxAttemptCount))
}
}
var attemptCount = 1
let source = Observable<Int>.create { observer in
let currentAttemptCount = attemptCount
print("#START => \(currentAttemptCount)")
// 2번째 시도까지 에러 방출.
if attemptCount < 3 {
// 다른 경우 : 6번째 시도까지 에러 방출. -> retry(5). 최대 5번의 시도이기 때문에 6번째에서 error(myError) 출력.
// if attemptCount < 7 {
observer.onError(MyError.myError)
attemptCount += 1
}
observer.onNext(1)
observer.onCompleted()
return Disposables.create {
print("#END => \(currentAttemptCount)")
}
}
source
// 최대 4번의 재시도.
.retry(5)
.subscribe {
print($0)
}
.disposed(by: disposeBag)
/*
#START => 1
#END => 1
// Error 이벤트가 방출되었기 때문에 재시도.
#START => 2
#END => 2
// Error 이벤트가 방출되었기 때문에 재시도.
#START => 3
next(1)
completed
#END => 3
*/
// 다른 경우에는 다음을 출력.
/*
#START => 1
#END => 1
#START => 2
#END => 2
#START => 3
#END => 3
#START => 4
#END => 4
#START => 5
#END => 5
error(myError)
*/
- retry(When:)
RxSwift 6.0 에서 retryWhen(_:)
이 retry(when:)
으로 이름이 바뀌었습니다.
https://github.com/ReactiveX/RxSwift/releases/tag/6.0.0
extension ObservableType {
/**
Repeats the source observable sequence on error when the notifier emits a next value.
If the source observable errors and the notifier completes, it will complete the source sequence.
*/
public func retry<TriggerObservable: ObservableType, Error: Swift.Error>(when notificationHandler: @escaping (Observable<Error>) -> TriggerObservable)
-> Observable<Element> {
RetryWhenSequence(sources: InfiniteSequence(repeatedValue: self.asObservable()), notificationHandler: notificationHandler)
}
trigger observable 을 만들어서 반환하게 되는데 이는 error 이벤트가 방출되었을 때 새로운 observable 을 만들고 해당 observable 의 next 이벤트가 방출되면 재시도하게 됩니다.
예를 들어, 서버통신이 실패한 뒤에 특정 시간 뒤에 다시금 재시도하는 Observable 을 만들 수도 있습니다.
즉, trigger observable 은 retry 를 trigger 하는데 사용됩니다.
retry 처럼 바로 재시도하는 것이 아니라 trigger observable 을 통해 retry 시점을 특정할 수 있습니다.
materialize / dematerialize
- materialize
materialize 는 시퀀스를 Event enum sequence(next, error, completed) 로 변환할 수 있습니다.
• Observable<Int>
→ Observable<Event<Value>>
extension ObservableType {
/**
Convert any Observable into an Observable of its events.
*/
public func materialize() -> Observable<Event<Element>> {
Materialize(source: self.asObservable())
}
}
materialize 를 사용하면 1 과 2, completed 혹은 error 가 이벤트로 변환되서 Event, Event
- dematerialize
materialize 로 변환한 Observable 을 원래 폼으로 전환할 수 있습니다.
• Observable<Event<Value>>
→ Observable<Int>
extension ObservableType where Element: EventConvertible {
/**
Convert any previously materialized Observable into it's original form.
*/
public func dematerialize() -> Observable<Element.Element> {
Dematerialize(source: self.asObservable())
}
}
어떻게 error handling 하나요?
materialize 와 dematerialize 를 사용해서 실패하는 sequence 를 디버깅해서 다룰 수 있습니다.
이는 third party 프레임워크에서 생성된 것처럼 시퀀스가 제한되거나 제어할 수 없을 때 해결할 수 있습니다.
또한, observable 를 제어할 수 없고, 외부 시퀀스가 종료되지 않도록 오류 이벤트를 처리하는 경우가 있습니다. materialize 와 dematerialize 를 함께 사용하면 observable 을 변환하여 이벤트를 처리하고 다시금 observable 로 변환시킬 수 있습니다.
- 아래 코드는 error 이벤트의 경우 next 이벤트로 바꿔주고 있습니다.
let observable = Observable<Int>
.create { observer -> Disposable in
observer.onNext(1)
observer.onNext(2)
observer.onNext(3)
observer.onError(NSError(domain: "", code: 100, userInfo: nil))
observer.onError(NSError(domain: "", code: 200, userInfo: nil))
return Disposables.create { }
}
observable
.materialize()
.map { event -> Event<Int> in
switch event {
case .error:
return .next(999)
default:
return event
}
}
.dematerialize()
.subscribe { event in
print(event)
}
.disposed(by: disposeBag)
// 출력 결과
next(1)
next(2)
next(3)
next(999)
completed
출처
4. Error Handling - catch, catchAndReturn, retry