안녕하세요.
이번에는 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 |