ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • RxSwift - retryWhen(_:)에 대해서 알아보자
    Ray Wenderlich/RxSwift 2020. 2. 20. 22:22


    Ch14. Error Handling in Practice p.292 - 294

    RxSwift의 에러처리 방법에 대해서 알아보자.


    크게 2가지가 있다.

    1. Catch하여 처리하기

    2. Retry하기 (Retry해도 실패하면 Catch하던가)


    1은 Swift와 유사하니 끄덕하고 넘어가자.

    2를 수행하기 위해 RxSwift에서는 retry() 오퍼레이터를 제공한다.


    retry를 시도하는 3가지의 메소드가 있다.

    func retry() -> RxSwift.Observable<Self.E> // 1


    func retry(_ maxAttemptCount:) -> Observable<E> // 2


    func retryWhen(_ notificationHandler:) -> Observable<E> // 3


    1, 2번의 경우는 직관적이다. 

    에러가 발생할 경우 시퀀스를 재생성하여 Error가 나지 않기를 기대하는 것이다.

    1과 2의 차이점이 있다면 횟수를 설정할 수 있는 정도??


    재생성해도 Error가 발생했다면 어쩔 수 없다. Catch해서 에러를 핸들링하는 수 밖에


    3번의 경우는 좀 특별한 파라미터를 받고 있다. notificationHandler의 타입은 TriggerObservable이다.

    (옵저버블 시퀀스라고 봐도 무방함. 단순한 Observable일수도, Subject일 수도 있음)


    에러 발생 상황

    API콜을 하려고 한다. 그런데 인터넷 연결이 오프라인이라면? API 응답이 예상과 다르게 내려와 에러가 발생한다면?

    API콜을 재시도하여 정상적인 결과값이 내려오길 기대할 수 있다.


    그런데 에러가 발생하자마자 바로 재시도하는 것 보다, 1초 뒤에 재시도 하는게 더 바람직한 것 같다.

    로그를 통해 상상해보자.


    subscription -> error 

    delay and retry after 1 second 


    subscription -> error 

    delay and retry after 3 seconds 


    subscription -> error 

    delay and retry after 5 seconds 


    subscription -> error 

    delay and retry after 10 seconds



    RxSwift를 사용하기 전에 우린, NSOperation이나 GCD 등을 사용하여 이를 구현했다.

    하지만 RxSwift에서는 retryWhen 오퍼레이터를 사용하여 구현할 수 있다.



    코드 작성

    우리는 최대 4번까지 재시도하는 코드를 작성해보자.

    let maxAttempts = 4


    subscribe하고자 하는 Observable에 아래 코드를 추가한다.

    .retryWhen { e in

    // flatMap source errors

    // 여기서 Observable을 리턴시키면 그 Observable을 만족시키도록(?) retry를 한다.

    // 이 Observable을 알맞게 작성하면 재시도 횟수, 재시도 interval 등을 조정할 수 있다. 뭔소린지 모르겠죠? 저도 몰겠음😭 아래 코드로 봅시다 ㅠㅠ


    // 만약 Observable.error를 리턴한다면 더 이상 retry를 하지 않고 error를 전달한다.

    }



    이 내부 클로져를 어떻게 작성할까??

    재시도 횟수를 카운팅하기 위해 enumerated()를 사용할 것이다.

    재시도 횟수가 4에 도달하면 error를 그대로 전달하게 될 것이고, 그렇지 않다면 적절한 딜레이가 있는 Observable<Int>하나를 생성하여 리턴할 것이다.

    .retryWhen { e in 

    e.enumerated().flatMap { (attempt, error) -> Observable<Int> in 

    // attempt few times 

    if attempt >= maxAttempts - 1 { 

    return Observable.error(error) 

    return Observable<Int>.timer(Double(attempt + 1), scheduler: MainScheduler.instance).take(1)

    }

    }




    그림으로 살펴보면 아래와 같다.



    요약

    retryWhen에 전달되는 Trigger는 이렇게 생각하면 쉽다.

    Observable<Error>를 다른 Observable로 변형시켜 리턴한다. 


    이 때, Error가 아닌 next이벤트가 전달되는 동안 끊임없이 재시도한다.


    특정 조건에 도달했을 때(재시도를 포기할 때), Observable.Error을 리턴해주자.


    난 이렇게 자주 사용할 것 같다.


    let retryHandler: (Observable<Error>) -> Observable<Int> = { error in

    error.flatMap { e in

    return (e as NSError).code > 900 

    Observable.error(e) 

    Observable<Int>.timer(1, scheduler: MainScheduler.instance).take(1) 

    }

    }


    observable.retryWhen(retryHandler).subscribe(....)



    깔끔하고 정돈된 느낌이구만!!

Designed by Tistory.