본문 바로가기
iOS/RxSwift

[RxSwift] Erro 처리 catch, retry 연산자

by 최지철 2023. 10. 31.
728x90
반응형

OnError 로 처리하면 되지 않낭?

  • Error 이벤트는 Observable 을 종료시키게 된다. 그래서 종료시키지 않고 다음과 같이 이벤트를 발생키시고 completed 가 발생되도록 error handling 을 하고자 하는 것이당!

RxSwift 에서는 보통 세가지 방법으로 Error Handling 을 할 수 있다.

  1.  catch : 특정 값으로 Error 복구
  2.  retry : 재시도 하기
  3.  materialize / dematerialize : Sequence 를 제어해서 처리

 

Catch

특정 값으로 Error 복구

  • Catch연산자는 Next와 Completed 연산자는 그대로 방출한다.
  • API 요청 코드에서 Catch가 많이 사용된다.
  • Error이벤트가 전달되면 새로운 Observable을 전달한다.
  • 네트워크 에러가 있을 경우, UI가 업데이트가 안된다 -> Observable에 기본값이 로컬캐시값을 담아서UI업데이트 하는 형식으로 사용한다
        enum TestError: Int, Error {
                case notFound = 200
                case noInternet = -1
            }
        let observable = Observable<Int>
            .create { observer -> Disposable in
                observer.onNext(1)
                observer.onNext(2)
                observer.onNext(3)
                observer.onError(TestError.notFound)
                observer.onError(TestError.noInternet)
                
                return Disposables.create { }
        }
        
        observable
            .catch { err -> Observable<Int> in
                let testError = err as? TestError
                
                return .just(testError?.rawValue ?? -1)
            }
            .subscribe(onNext: {
                print($0)
            })
            .disposed(by: disposeBag)
        
        /*
         1
         2
         3
         200
         */

CatchErrorJustReturn

  • 에러를 만다면, 파라미터 값을 그대로 방출한다.
  • 주로 에러발생 시, 기본값 혹은 로컬 캐시값을 방출할 때 쓰인다.
let subject = PublishSubject<Int>()

subject
    .catchAndReturn(0)
    .subscribe { print($0) }
    .disposed(by: disposeBag)

subject.onError(MyError.error) // next(0)

 

Retry

  • 소스 Observable에서 에러 발생시, Observable에 대한 구독을 해체하고 새로운 Observable에 구독을 실행한다.
  • 새로운 구독이 시작되기 때문에, Observable의 모든작업이 다시 시작된다.
  • 에러가 발생하지 않으면, 정상적으로 종료되고, 에러가 발생하면 또 다시 새로운 구독을 실행한다.
  • 파라미터에 횟수를 지정할 수 있지만 + 1해야한다. 즉 3으로 설정할 경우, 2번만 retry하게 된다.
var attemptCount = 1

let source = Observable<Int>.create { observer in
    let currentAttemptCount = attemptCount

    print("#START => \(currentAttemptCount)")

    // 2번째 시도까지 에러 방출.
    if attemptCount < 3 {
        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
*/

// maxAttemptCount 보다 많이 시도할 경우에는? Error 이벤트를 방출한다.
// 다음과 같이 작성하면 maxAttemptCount 가 넘었기 때문에 재시도 할 수 없다.
source
    // 최초 1번 시도.
    .retry(1)
    .subscribe(onNext: {
        print($0)
    }, onError: {
        print("error: \($0)")
    })
    .disposed(by: disposeBag)

/*
#START => 1
#END => 1
error: myError
*/

 

Retry(When:)

  • Error가 떨어졌을 때 error를 가공해 observerbleType을 반환한다.
  • 즉 다음 예제처럼 retry시점을 조작하거나 값을 변경할 수 있다.
  • closure를 파라미터로 받아서 해당 해당 closure가 완료되면 시도했던 로직 self로 다시 sequence로 만들어서 동작
  • subscribe()하기 전에 사용하고, retry(when:)의 클로저 내부에서 error.flatMap {}을 사용
    • Observable<Int>.timer를 통해서 언제 재시도를 할건지 정의
    • retry(when:) 다음 스트림에 take(3)을 넣어서 얼마만큼 반복할건지 명시
API.getName()
  .retry(when: { error in
    error.flatMap { _ in
      Observable<Int>
        .timer(.seconds(3), period: nil, scheduler: MainScheduler.asyncInstance)
    }
  })
  .take(3)
  .subscribe()
  .disposed(by: self.disposeBag)​
728x90
반응형