본문 바로가기

RxSwift

[RxSwift] Single Trait - wrapping network request

반응형

Single Trait

RxSwift에서는 Single Trait 을 지원합니다. Single은 Observable의 한 형태이며, 한 가지 값 또는 에러를 발행합니다.

따라서 구독 시, success와 error 두 가지의 이벤트에 처리를 할 수 있습니다.

대표적인 예로는 네트워크 요청 구독을 위해 request를 Single로 wrapping 하여 사용할 수 있습니다.

 

위의 예제는 기존 프로젝트에서 Alamofire를 사용하여 Request 하는 로직을 Single로 Wrapping 한 코드입니다.

네트워크 요청은 한 번의 API 콜을 통해 성공/실패 두 개의 이벤트만을 필요로 하기 때문에 Single을 사용하는 좋은 예제입니다.

 

Single을 사용하면 명시적으로 하나의 성공 혹은 실패 여부만을 관리한다는 것을 알 수 있으며, 두 개의 이벤트만 처리하기 때문에 코드가 줄어들며 간단명료합니다.

 

주의 사항
Stream에서 Single을 사용한다면 Single로 시작해야 합니다. Observable로 시작해서 중간에 asSingle로 바꿔 Single을 엮는다거나 하면 문제가 발생합니다. 이는 Single은 좁은 범위에서의 Observable 이기 때문입니다. Observable은 다량의 next 이벤트를 발행할 수 있으며, complete 또한 발행할 수 있습니다. 하지만 Single은 complete 이벤트를 발행할 수 없습니다. Single의 이벤트인 success 자체가 next, complete 두 개의 속성을 다 포함하고 있기 때문입니다.

 

github.com/ReactiveX/RxSwift/blob/master/RxSwift/Traits/Single.swift#L43

 

ReactiveX/RxSwift

Reactive Programming in Swift. Contribute to ReactiveX/RxSwift development by creating an account on GitHub.

github.com

 

위 링크의 코드를 보면 알 수 있듯이 complete 이벤트가 발행되었을 때, 이전에 next 이벤트가 들어오지 않으면 에러를 발행하게 됩니다. 

 

github.com/ReactiveX/RxSwift/blob/master/RxSwift/Observables/SingleAsync.swift#L81

 

ReactiveX/RxSwift

Reactive Programming in Swift. Contribute to ReactiveX/RxSwift development by creating an account on GitHub.

github.com

또한 asSingle을 사용하여 Observable을 Single로 변환하여 사용할 경우에도, complete 이벤트가 발행되었을 때 이전에 next 값이 없다면 에러를 발행하게 됩니다.

따라서 Single을 사용해야 한다면 반드시 Single로 시작하도록 하고, Observable로 시작한 경우 불필요한 에러를 발행할 수 있기 때문에 가급적 지양하는 것이 좋습니다.

 

Observable 특성으로 인해 발생할 수 있는 문제점

보통 위와 같이, View의 User Interaction을 ViewModel에 binding 하고, 해당 이벤트가 발생한다면 Network Request 로직을 거치도록 구현할 수 있습니다.

 

일반적으로 바라는 상황은 버튼을 눌렀을 때, 예외 상황(time out 등)이 발생하여 Network Request가 실패했다 하더라도 다음에 다시 누르더라도 다시 시도하는 상황을 바랄 것입니다.

 

하지만, Network Result가 fail이 될 경우, 이를 Single의 onError 이벤트를 발행하도록 Wrapping 했기 때문에 error를 발행한 이후 dispose 처리됩니다. 따라서 다시 binding하지 않는 이상 Network Request에 실패했다면 구독이 처분되었기 때문에 이벤트를 받을 수 없습니다.

 

따라서 위와 같이 NetworkResult 자체를 success 이벤트로 발행함으로써, Network Request가 실패하더라도 구독을 유지할 수 있습니다.

 

이상하다고 느껴지는 것이 있나요~?

success 이벤트 자체에 next, complete 속성을 지니고 있으므로 success가 발행되더라도 dispose 되어야 하는 것이 아닐까요~?

flatMap 연산자를 이용하지 않고 개별적으로 사용한다면, success 이벤트가 발행되면 complete 이후 dispose 처리가 되는 것이 맞습니다.

하지만 flatMap 연산자를 사용하는 경우, 원본 Observable(위 예제에서는 buttonTapped)이 기준이 되기 때문에 원본 Observable이 complete가 나지 않는 이상 flatMap을 통해 발행되는 complete는 무시되게 됩니다.

 

위 코드를 보면 알 수 있듯이, flatMapLatest 클로져에서 Observable을 생성하고, next와 complete 이벤트를 같이 발행했지만 1~10까지 모든 이벤트가 발행된 이후 complete가 되는 것을 확인할 수 있습니다.

 

하지만 에러의 경우는 다릅니다.

위 테스트 결과를 보면 알 수 있듯이, flatMapLatest 클로져에서 에러 이벤트를 발행하면 에러가 발생하고 dispose 되는 것을 볼 수 있습니다.

 

예상컨대, flatMap은 원본 Observable에서 next를 통해 발행된 아이템들을 다른 타입으로 변환하는 과정이므로, next 또는 변환 과정에서 발생된 error만을 취급하는 것으로 이해했습니다. (따라서 클로져 내에서 complete를 발행하더라도 무시되는 것)

 

결론

  • RxSwift를 Single Trait을 제공하며 이의 적합한 예제는 Network Request로 활용하는 것이다.

  • 네트워크 요청 실패를 onError로 발행할 경우, flatMap 등의 연산자를 통한 구독 관리가 어려워진다.

  • Network Result 자체를 success 이벤트로 발행하여 에러 핸들링과 동시에 구독 관리를 원활하게 할 수 있도록 할 수 있다.

 

반응형