티스토리 뷰

OpenSource/RxSwift

RxSwift#5 ErrorHandling

malrang-malrang 2022. 9. 28. 15:11

RxSwift ErrorHandling

ErrorHandling

  • 앱에서 발생하는 가장 흔한 에러들은 다음과 같다.

    • 인터넷 연결 없음: 아주 흔한 에러 중 하나다. 만약 앱이 인터넷 연결을 통해 데이터를 받아와야하는데 기기가 오프라인 상태가 된다면, 이를 감지하고 적절한 대응을 해줘야 한다.
    • 잘못된 입력: 때에 따라서 정해진 폼에 따라서 입력값이 필요한 경우가 있다. 하지만 사용자는 언제든지 잘못된 값을 입력할 수 있다. 전화번호 입력란에 숫자대신 글자를 입력하는 사용자는 언제나 있을 수 있다.
    • API 또는 HTTP 에러: API를 통한 에러는 아주 광범위하게 일어난다. 표준 HTTP 에러(400 또는 500 에러)를 통해 표시되거나 JSON 내 status 필드를 통해 표시될 수 있다.
  • RxSwift에서는 크게 세가지 방법으로 Error Handling을 할 수 있다.

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

1 .catch

  • Swift의 do/try/catch와 유사하다.
  • 에러가 발생했을 때 Error이벤트로 종료되지 않게 한다.
  • Error 이벤트 대신 특정 값의 이벤트를 발생시키고 complete시킨다.
  • RxSwift에는 catch 계열에 두가지 연산자가 있다.

.catchError(_ handler: (Swift.Error)-> Observable)

  • Error를 다른 타입의 Observable로 반환하는 클로저를 parameter로 받는다.
  • Error가 발생했을 때 Error를 무시하고 클로저의 반환값 Observable<E> 을 반환한다.
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
    .catchError { .just(($0 as NSError).code) }
    .subscribe { print($0) }
    .disposed(by: disposeBag)

/* Prints:
    next(1)
    next(2)
    next(3)
    next(100)
    completed
*/

catchErrorJustReturn(_ element: E)

  • Error가 발생했을 때 Error를 무시하고 element를 반환한다.
  • 모든 에러에 동일한 값이 반환되기 때문에 catchError에 비해 제한적이다.
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
    .catchErrorJustReturn(999)
    .subscribe { print($0) }
    .disposed(by: disposeBag)

/* Prints:
    next(1)
    next(2)
    next(3)
    next(999)
    completed
*/

2. retry

  • 말 그대로 에러가 발생 했을 때 다시 시도할 수 있게 해준다.
  • 에러가 발생했을 때 Observable을 다시 시도한다(Observable 내의 전체 작업을 반복한다.)
  • 아래 사진처럼 네트워킹 요청에서 에러가 발생할 경우 retry를 사용해 다시 시도할 수 있다.
  • RxSwift에는 retry 계열에 세가지 연산자가 있다.

.retry()

  • 일반적인 재시도에 사용하는 연산자, 에러가 발생했을 때 성공할 때 까지 Observable을 다시 시도한다.
  • 아래의 코드는 네트워킹을 통해 repositories를 가져오는 예시코드다. 인터넷 연결을 끊고 reload를 하면 콘솔에 실패 메세지가 게속 찍히는것을 확인할수 있다.
let reloadPublisher = PublishSubject<Void>()

reloadPublisher
  .flatMap {
    Api.getRepositories()
      .retry()
  }

.retry(_ maxAttemptCount: Int)

  • 최대 몇번 재시도할지 지정할 수 있는 연산자이다.
  • maxAttemptCount가 3이라면 총3번의 요청을 보낸다.(2번은 재시도)
  • 재시도 횟수가 넘어가면 그대로 Error를 이벤트로 전달한다.
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
    .retry(2)
    .subscribe { print($0) }
    .disposed(by: disposeBag)

/* Prints:
  next(1)
  next(2)
  next(3)
  next(1)
  next(2)
  next(3)
  error(Error Domain= Code=100 "(null)")
*/

.retryWhen

  • 재시도 하는 시점을 지정할 수 있고, 한번만 수행한다.
  • retry와 다르게 마지막 Error를 이벤트로 전달하지 않는다.
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
    .retryWhen { err -> Observable<Int> in
        return .timer(3, scheduler: MainScheduler.instance)
    }
    .subscribe { print($0) }
    .disposed(by: disposeBag)

/* Prints:
    next(1)
    next(2)
    next(3)
    (3 seconds)
    next(1)
    next(2)
    next(3)
    completed
*/

3. Materialize / Dematerialize

  • RxSwift에서 쓰이는 Event Sequence를 제어할 수 있게 해준다.
  • 보통 Materialize / Dematerialize 함께 사용한다.
  • Observable을 분해할 수 있기 때문에 신중하게 사용해야 한다.

Materialize

  • Sequence 를 Event Sequence로 변환한다.

Dematerialize

  • Event Sequence 를 다시 Sequence로 변환한다.
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)

/* Prints
  next(1)
  next(2)
  next(3)
  next(999)
  completed
*/
  • 위의 예제에서 materialize를 사용해 event로 변환후 error일 경우 next로 교체해준 모습을 볼 수 있다.

참고한 문서및 자료

https://okanghoon.medium.com/rxswift-5-error-handling-example-9f15176d11fc

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함