티스토리 뷰

728x90
반응형
  1. catch : error 를 새로운 Observable 또는 값으로 처리
  2. retry : 재시도
  3. materialize / dematerialize : sequence 를 제어해서 처리

onError 에서 처리하면 되지 않나요?

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

1

catch

  • catch

RxSwift 6.0 에서 catchError(_:)  catch(_:) 로 이름이 바뀌었습니다. 출처 - RxSwift release

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)
    }
}

2

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 에서 catchErrorJustReturncatchAndReturn 으로 이름이 바뀌었습니다. 출처 - RxSwift release

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) })
    }
}

4

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()))
    }

5

maxAttemptCount 파라미터로 최대 retry 횟수를 지정할 수 있다. 이때는 처음 시도 횟수도 포함한다.

extension ObservableType {
    /**
     Repeats the source observable sequence the specified number of times in case of an error or until it successfully terminates.

     If you encounter an error and want it to retry once, then you must use `retry(2)`
     */
    public func retry(_ maxAttemptCount: Int)
        -> Observable<Element> {
        CatchSequence(sources: Swift.repeatElement(self.asObservable(), count: maxAttemptCount))
    }
}

6

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:)

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)
    }

9

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())
    }
}

10

materialize 를 사용하면 1 과 2, completed 혹은 error 가 이벤트로 변환되서 Event, Event, Event 가 됩니다. 따라서 onNext, onError, onCompleted 각각의 함수로 핸들링할 수 있습니다.

  • 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())
    }

}

스크린샷 2023-02-11 오후 3 06 05

어떻게 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

https://github.com/fimuxd/RxSwift

Materialize/dematerialize

RxSwift, Error 처리

RxSwift) Error Handling Operators

728x90
반응형

'iOS > RxSwift' 카테고리의 다른 글

Trait(2) ControlProperty, ControlEvent  (0) 2023.03.04
RxSwift) Traits(1) - Driver, Signal  (0) 2023.02.27
RxSwift) 6.Subjects  (0) 2022.07.14
RxSwift) 5.Observables  (0) 2022.07.12
RxSwift) 3. App architecture & RxCocoa  (0) 2022.07.01
댓글
최근에 올라온 글
최근에 달린 댓글
글 보관함
«   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
링크
Total
Today
Yesterday