티스토리 뷰
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
'OpenSource > RxSwift' 카테고리의 다른 글
RxCocoa text (키워드 입력할때마다 Request요청하기) (1) | 2022.10.23 |
---|---|
RxSwift#4-3 Operators (Combining Operators) (0) | 2022.09.24 |
RxSwift#2 Observable (2) | 2022.09.23 |
RxSwift#3 Hot Observable vs Cold Observable, Subjects + Relay (0) | 2022.09.22 |
RxSwift#1 RxSwift의 개념과 용어들 (0) | 2022.09.22 |