Rx

[RxSwift 6.1] withUnretained, subscribe(with:onNext:etc)

Phililip
728x90

안녕하세요.

 

이번에는 RxSwift 6과 RxSwift 6.1에 새롭게 추가된 withUnretained operator와 subsubscribe(with:onNext:onError:onCompleted:onDisposed:) API에 대해 알아볼게요.

 


# 1. withUnretained

Swift 클로저는 내부에서 self에 접근해야 할 때 주의해야 합니다.

 

바로 retain cycle 때문인데요.

(더 자세한 내용은 클로저에서 [weak self] 사용할 때 주의할 점3 글을 참고해주세요.)

 

 

그래서 클로저 안에서 self의 약한 참조를 갖기 위해 아래처럼 [weak self]를 사용합니다.

(guard-let 구문 하고 세트로도 자주 사용하죠ㅎㅎ)

 

 

 

즉, RxSwift에서는 대부분 클로저를 사용하기 때문에 [weak self] 구문이 계속해서 중복된다는 얘기로 이어져요.

 

이럴 때 withUnretained operator를 사용하면 코드를 깔끔하게 관리할 수 있게 됩니다.

 

[여담] 원래는 RxSwiftExt에서 만든 operator이지만 RxSwift 6에서 정식으로 흡수되었다고 하네요ㅎㅎ

 

 

아래는 위에서 소개한 예시를 withUnretained operator를 사용했을 때의 코드입니다. 

 

 

클로저 내부를 완성하기 전 코드를 다시 살펴보면

 

 

subscribe 하기 전 withUnretained에서 넘긴 인스턴스가 onNext 클로저를 통해 튜플 형태로 전달됩니다.

 

 

저는 여기서 3가지 의문이 들었어요.

- onNext 클로저로 넘어온 인스턴스가 약한 참조로 걸려있는 게 맞나??
- 왜 onError 클로저에서는 인스턴스가 전달 안됐나??
- onError 클로저에서도 인스턴스를 전달받고 싶은 경우에는??

 

 

withUnretained 내부 소스코드를 보면 위 의문점들이 풀리게 됩니다ㅎㅎ 한번 살펴볼까요??

 

 

 

26번째 줄에서 guard-let을 이용해 Object에 대해 약한 참조를 걸고

 

28번째 줄에서 resultSelector 클로저를 호출합니다. resultSelector 클로저는 내부에서는 Object와 Element를 튜플 형태로 묶어서 반환해주고 있습니다.

 

이걸로 첫 번째 의문은 해결이 되었죠??ㅎㅎ

 

 

 

두 번째 의문은 사실 당연한 거예요.

 

에러 이벤트는 

 

 

 

이런 형식이기 때문에 별도의 처리가 들어가지 않는 한 이 규칙을 따라줘야 해요.

 

그래서 onError의 경우 Object를 넘기지 않는(못하는) 것이죠.

 

 

 

그럼 자연스럽게 세 번째 의문으로 넘어갑니다.

 

withUnretained를 써도 onError 클로저에서는 Object를 못 받네???

-> OK. 그럼 onError 클로저에서도 Object를 받고 싶으면 어떻게 해???

 

밑에서 소개할 subscribe(with:onNext:onError:onCompleted:onDisposed:)를 사용하면 쉽게 해결이 가능합니다ㅎㅎㅎ

 

 

 

subscribe(with:onNext:onError:onCompleted:onDisposed:)에 대해서 소개하기 전에 withUnretained를 사용할 때 주의점에 대해 말씀드려볼까 해요.

(subscribe(with:onNext:onError:onCompleted:onDisposed:)가 나오게 된 배경하고 관련이 있거든요)

 

 

 

withUnretained operator는 share(replay: 1)과 함께 사용하면 안 됩니다.

 

share(replay:1)과 함께 사용하면 특정 상황에서 retain cycle이 발생할 수 있다고 해요.

(Object를 버퍼에 저장하고 share로 인해 버퍼가 계속 살아있어서... 약한 참조로 걸었지만... 클로저 안에서 guard-let으로 붙잡고 있기 때문에 강한 참조가 걸려있는 것처럼 보여서 그러나....? 근데 약한 참조로 걸었는데...?... 흠..... 혹시 정확하게 아시는 분 계시면 알려주세요ㅠㅠㅠ)

 

 

그래서 RxSwift 6.1부터 withUnretained operator는 Driver에서 사용 못하도록 hard deprecated 되었습니다.

(Driver 내부적으로 share(replay:1)을 사용하기 때문이죠)

 

 

 

대신에 drive(with:onNext:onCompleted:onDisposed:)를 사용하라고 합니다. 이 내용은 아래에서 이어서 할게요!

 

 

 

 

# 2. subscribe(with:onNext:onError:onCompleted:onDisposed:)

withUnretained operator는 만능처럼 보이지만 몇 가지 단점이 있었습니다.

- onError에서는 여전히 [weak self]를 써야 한다.
- share(replay:1) 하고 같이 사용할 경우 retain cycle이 발생할 수 있다.

 

 

첫 번째 문제는 그렇다 치고 두 번째 문제는 side-effect 이기 때문에, 심각한 문제죠...

 

그래서 RxSwift 6.1부터 withUnretained operator를 대신할 subscribe(with:onNext:onError:onCompleted:onDisposed:) method를 새롭게 추가하였습니다.

 

 

Add new subscribe(with:onNext:onError:onCompleted:onDisposed:) alternatives to withUnretained. This exists for all traits and types: Observable, Driver, Signal, Infallible, Completable, Single, Maybe #2290

출처: https://github.com/ReactiveX/RxSwift/releases/tag/6.1.0

 

 

(개인적으로는 withUnretained 보다 subscribe(with:onNext:...) method를 더 권장하는 것처럼 느껴졌는데 저만 그런가요..? ;;;)

 

 

사용법은 기존의 subscribe(onNext:onError:onCompleted:onDisposed:) 와 동일하고, 대신 with로 약한 참조를 할 인스턴스를 넘겨주면 됩니다.

 

 

withUnretained operator랑 다르게 onError, onCompleted, onDisposed 클로저에서도 인스턴스가 전달되니 편해졌네요!!ㅎㅎ

 

 

괜히 궁금해서 내부 구현을 봤더니,

 

 

 

모든 클로저 안에서 [weak object] + guard-let 구문을 사용해주고 있었습니다ㅎㅎㅎㅎ 👍 👍 👍

 

 

또한 subscribe의 wrapper method들도 여러 개 존재하는데요.

- bind
- emit
- drive

 

 

이 모든 method들도 with 파라미터를 넘길 수 있도록 추가되었으니 참고해주세요!

 

 

 

 

 

 

# 참고

- https://github.com/ReactiveX/RxSwift/releases

- https://github.com/ReactiveX/RxSwift/pull/2290

- https://dev.to/freak4pc/what-s-new-in-rxswift-6-2nog

 

 

 


 

이번 글은 여기서 마무리.

 

 

 

반응형

'Rx' 카테고리의 다른 글

[RxDataSources] RxTableViewSectionedAnimatedDataSource  (0) 2022.11.12
[RxDataSources] RxDataSources 맛보기  (0) 2022.11.06
[ReactorKit] @Pulse  (0) 2022.09.30
[ReactorKit] ReactorKit 입문  (0) 2022.09.11