catch, retry
* 이 포스트는 RxSwift 4.3.1, swift 4.2 버전을 기준으로 작성되었습니다.
에러발생을 감지하는 catchError 메서드와 다시 정상적인 이벤트를 받기 위한 retry 에 대해 알아보자.
에러가 발생됐을때 onError 로 종료되지 않도록 하고, 이벤트를 발생하고 onComplete 될수 있도록 한다.
예제
let zoo = Observable<String>.create { observer in
for count in 1...3 {
observer.on(.next("Rabbit\(count)"))
}
let error = NSError(domain: "dummyError", code: 0, userInfo: nil)
observer.on(.error(error))
return Disposables.create {
print("dispose")
}
}
zoo.catchError{ error -> Observable<String> in
return Observable.just("Zoo Closed")
}.subscribe(onNext: { test in
print(test)
}.disposed(by: disposeBag)
설명
"토끼" 이벤트를 3번 발생하고 에러를 전달하는 observable 을 생성했다.
catchError 에서 에러를 받았지만, 단일 이벤트 observable 연산자인 just 를 통해 "Zoo Closed" 이벤트를 전달하도록 했다.
결과
Rabbit1
Rabbit2
Rabbit3
Zoo Closed
dispose
catchError 에서는 여러개의 이벤트를 보내줄 수도 있다.
return Observable.of("Fox1","Fox2","Closed Zoo")
catchErrorJustReturn 은 위와 같이 error 가 발생했을때 설정해둔 단일 이벤트를 전달하도록 하는 연산자이다. subscribe 에서 에러를 감지 하는 것이 아닌, Observable 에서 에러에 대한 기본 이벤트를 설정하는 부분에 주목하자.
예제
let zoo = Observable<String>.create { observer in
for count in 1...3 {
observer.on(.next("Rabbit\(count)"))
}
let error = NSError(domain: "dummyError", code: 0, userInfo: nil)
observer.on(.error(error))
return Disposables.create {
print("dispose")
}
}.catchErrorJustReturn("Rabbit End")
zoo.subscribe(onNext: { test in
print(test)
}).disposed(by: disposeBag)
결과
Rabbit1
Rabbit2
Rabbit3
Zoo Closed
dispose
에러가 발생했을때 성공을 기대하며 Observable 을 다시 시도하는 연산자이다.
(maxAttemptCount 를 통해 재시도 횟수를 지정할수 있다. 2로 지정하면 에러를 처리하고 다시한번 수행한다는 것이다. retry(2)가 재시도를 한번 한다는 뜻)
예제
let zoo = Observable<String>.create { observer in
observer.on(.next("TEST 1"))
observer.on(.next("TEST 2"))
let error = NSError(domain: "dummyError", code: 0, userInfo: nil)
observer.on(.error(error))
//observer.on(.completed)
return Disposables.create()
}
zoo.debug().retry(2).subscribe { (event) in
print(event)
}.disposed(by: disposeBag)
설명
2번의 이벤트 이후 에러를 발생하는 observable 을 생성했다. 이후 retry(2)를 통해 observable 이 다시 수행된다.
결과
빨간색은 Observable의 debug 로그이다.
파란색은 Subscriber의 이벤트 로그이다.
-> subscribed
-> Event next(TEST 1)
next(TEST 1)
-> Event next(TEST 2)
next(TEST 2)
-> Event error(Error Domain=dummyError Code=0 "(null)")
-> isDisposed
-> subscribed
-> Event next(TEST 1)
next(TEST 1)
-> Event next(TEST 2)
next(TEST 2)
-> Event error(Error Domain=dummyError Code=0 "(null)")
-> isDisposed
error(Error Domain=dummyError Code=0 "(null)")
첫번째는 에러를 감지하고, retry 했지만 두번째 에러는 처리하지 않도록(maxAttemptCount: 2) 했기 때문에 에러가 전달된다.
retry 하는 시점을 지정할 수 있다. 재시도는 한번만 수행한다.
위 그림에서 볼수 있듯, 에러가 발생했을때 retryWhen에서 지연시간을 가질 Observable 을 지정하면,
이 Observable 의 이벤트 가 발생 했을때 다시 Observable 의 시퀀스가 수행한다.
retry 와 이벤트 전달할때 하나의 차이점이 있다. 아래쪽 결과 로그를 보면서 설명 (!!)
위의 예제중 retry를 retryWhen 으로 바꿔보자.
let zoo = Observable<String>.create { observer in
observer.on(.next("TEST 1"))
observer.on(.next("TEST 2"))
let error = NSError(domain: "dummyError", code: 0, userInfo: nil)
observer.on(.error(error))
return Disposables.create()
}
zoo.debug().retryWhen { (_) -> Observable<Int> in
return Observable<Int>.timer(3, scheduler: MainScheduler.asyncInstance)
}.subscribe { (event) in
print(event)
}.disposed(by: disposeBag)
결과
-> subscribed
-> Event next(TEST 1)
next(TEST 1)
-> Event next(TEST 2)
next(TEST 2)
-> Event error(Error Domain=dummyError Code=0 "(null)")
-> isDisposed
-> subscribed
-> Event next(TEST 1)
next(TEST 1)
-> Event next(TEST 2)
next(TEST 2)
-> Event error(Error Domain=dummyError Code=0 "(null)")
-> isDisposed
completed
retry 와 차이점은 마지막 error 를 이벤트로 전달하지 않는다는 점이다.
retryWhen 은 complete 으로 종료되었다.
관련해서 RxSwift 에서 논의된 Issue 링크를 첨부한다.
RxJS에서는 error 로 떨군다고 하는데 왜 RxSwift 에서는 completed가 되는가에서 시작된 이슈이다.
RxSwift 의 개발자는 RxJava 가 많이 쓰이기때문에, RxJava 의 동작을 따랐다고 했다. :)
https://github.com/ReactiveX/RxSwift/issues/1082
사족: 그럼 RxJava는 retry when 은 한번만 수행되는게 아닌데 왜 그건 안따라했지? :(
이벤트가 일정시간동안 발생하지 않으면 오류를 발생시킨다.
예제
let timer = Observable<Int>.create { observer in
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer.schedule(deadline: DispatchTime.now() + 1, repeating: 1)
let cancel = Disposables.create {
timer.cancel()
}
var next = 0
timer.setEventHandler {
if cancel.isDisposed {
return
}
if next < 3 {
observer.on(.next(next))
next += 1
}
}
timer.resume()
return cancel
}
timer.debug().timeout(2, scheduler: MainScheduler.instance).subscribe().disposed(by: disposeBag)
결과
-> Event next(0)
-> Event next(1)
-> Event next(2)
(2초 뒤)
Unhandled error happened: Sequence timeout.
1초에 한번씩 이벤트를 발생시키는 옵저버블이지만 3번 이벤트 발생뒤에는 이벤트를 발생하지 않도록 했다.
타임아웃을 2초로 설정했기 때문에 3번째 이벤트까지는 타임아웃이 걸리지 않았으며(1초간격 이므로)
3번째 이벤트 이후 2초뒤 타임아웃을 통해 에러가 발생한 것을 확인 할 수 있다.
에러 처리 연산자들을 통해 상황에 따라 유연한 처리가 가능하다.