본문 바로가기
iOS

[iOS] 내가 잘못쓰고 있었던 RxSwift 모음. 당신도 혹시...?

by 코코종 2023. 12. 27.

안녕하세요 코코종입니다 :) 2주만에 결국 돌아왔습니다..! (물론 마감에 쫓기고 있는 상태지만요 허허..)

이번에는 RxSwift를 다시 공부하며 잘 이해하지 못했거나 잘못 사용하고 있던 개념이 많다는걸 깨닫고 도움이 되고자 몇가지 적어보려고 합니다. 몇가지 목록으로 적어봤는데 상당히 잘 모르고 썼더라구요…? 그래서 반성겸 다른 분들은 꼭 알고 쓰기를 바라는 마음에서 적어봅니다

이미 알고 계신게 많겠지만! 혹시 모르니까~! 한번 읽어보시죵

*RxSwift에 대한 개념들을 다 소개하는게 목적이 아니기 때문에 ‘아렉수가 뭔데요~~’ 라고 하시면 ‘다른분 블로그 보고 공부하세요~~’라고 답하겠습니다. ^_^

 

1. 이중,삼중으로 구독하기

삼중 구독문!!

처음에 RxSwift를 사용하며 많이 저질렀던 실수(?)중에 하나입니다. 가장 기본이 되는 ‘구독! 방출!’ 요 두가지는 이해했는데 아렉수에 대한 이해가 부족했기 때문에 제일 많이 했던 실수입니다.

하나의 동작이 트리거가 되거나 혹은 어떤 옵저버블의 결과를 가지고 다른 스트림을 생성해줘야 할 때 위와 같이 이중 혹은 삼중으로 구독을 해서 사용했던 경험이 많습니다 (부끄)

이는 위와 같이 flatmap을 활용해 리팩토링 할 수 있습니다. 실제로 요것이 권장되는 방식입니다. (자 빨리 코드에 적용하세요)

 

 

이전에는 map과 flatMap의 차이점에 대한 설명을 아무리 읽어도 평탄화, 옵저버블의 옵저버블 뭐 요런 이야기가 많이 나왔는데 이해가 안되었는데 이중, 삼중 구독을 flatmap으로 해결한다! 라는 부분에서 아하!! 라는 포인트가 왔습니다(물론 딱 동일한건 아닙니다만)

RxSwift +  네트워크 통신을 처음 배울때 flatmap으로 처리했다는 사실을 뒤늦게 이해하고 넘어갔던 기억이 있습니다 :)

 

여담으로 제가 멘토로 활동하는 새싹 3기생분들에게 '왜 flatmap 쓰나요??, map과 flatmap의 차이는 뭔가요??' 라며 물음표 살인마를 날렸었는데 저처럼 모르고 사용하지 않기를 바라는 마음이었답니다. 괴롭히려고 그랬던거 절대 아님 😇😇

 

https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Tips.md

RxSwift 공식 문서를 살펴보면 'code smell' 이라고 표현하고 있네요. 그런데 왜 위와같이 이중으로 구독하면 안될까요?

 

1. 메모리 관리 차원

메모리 낭비

이중으로 구독을 하게 될 경우 내부의 구독이 계속 살아있게 되며 이는 버튼을 누를때 마다 새로운 구독이 생성되어 메모리의 낭비가 발생할 수 있습니다. 내부의 스트림은 독립적이기 때문에 발생합니다.

2. 가독성 차원

무한 콜백지옥과 마찬가지로 클로저 내부에 클로저가 존재하게 되며 그렇기에 가독성이 많이 떨어지게 됩니다. 또한 disposeBag을 관리하는 self에 대한 처리가 점점 불편해지게 됩니다.

3.  stream 관리 차원

순서대로 동작하는것을 보장하지 못함

- 방출된 결과로 새로운 스트림을 생성하는 것이기 때문에 비동기 처리 과정에서 원하는 순서대로 동작하지 않을 수 있습니다.

 

error등으로 스트림이 dispose 되는것이 예상과 다름

- flatmap을 사용한 예시의 경우 변환된 스트림이 error를 방출할 경우 전체 스트림이 종료되어서 다시 버튼을 눌러도 next 이벤트가 발생하지 않습니다. 그러나 다중 구독을 한 경우 내부의 스트림만 끊어져 버튼의 next 이벤트는 계속 받게됩니다.

 

 

2. 이미 Share되는 스트림을 또 share하기

Trait의 특성을 제대로 알지 못해서 발생했던 문제입니다.

예를들어 Driver와 Signal 같은 경우 내부적으로 share가 구현되어 있지만, 이를 모르고 '우헤헤헤 스트림 공유되도록 share 써야지'라고 했던 경우가 종종 있습니다. (반성)

 

+ 간혹가다가 Subject 자체에도 share와 같은 '스트림이 공유가 된다' 라는 블로그 글을 몇가지 봤는데 진리의 코테코뿐 아니라 subject에 관한 구현부를 찾아봐도 share에 대한 부분은 찾지 못했습니다..! (혹시나 제가 잘못 알고있는 것이라면 알려주세요 😇 )

 

3. asObservable, asDriver 등 as를 남발하기

약간 과장을 더하자면 요런 모습이 아닐까...

 

driver를 옵저버블로, 옵저버블을 드라이버로 신나게 변환해서 사용했는데 상당한 비효율의 끝판왕이었습니다. 뷰모델에서는 RxCocoa를 import하지 않으려다보니 Subject 타입이었고 이를 driver로 변환해서 사용했습니다. 그렇다보니 앞서 말한 share의 특성을 사용하지 못했고 subject를 여러번 구독하며 그때마다 asDriver를 붙여주곤 했습니다 ^_^.... 반성

 

4. Singal 안쓰기

시그널 보내~ 시그널 보내~ 찌릿 찌릿 찌릿 찌릿

이것도 Trait을 잘 몰라서... 잘못 사용했습니다(또..?)

모든곳에 subject를 사용했고 'UI 요소에는 driver를 사용하는게 국룰!' 이라고만 잘못 알고 있었고, 사실 시그널이 뭔지도 잘 몰랐습니다 ^^...

간단히 짚고 넘어가자면 Driver와 동일하게 error를 발생하지 않고 share가 되며 메인 스케쥴러에서 동작하고 다른점이라고는 driver와는 다르게 replay가 되지 않는다는 특성을 가진 Trait입니다.

 

혹시 replay가 되지 않는 특성! 하면 PublishSubject와 BehaviorSubject가 생각나지 않으시나요?? (아님 말고)

얘들과 역할이 상당히 유사하기 때문에 Subject의 wrapper로 쓰이는 Relay에서는 이에 맞게 signal, driver로 error에 대해 처리가 없이 단순히 변환해주는 메서드가 존재합니다. (그래서 BehaviorRelay 쓸때는 asDriver() 요렇게 에러에 대한 처리가 없던것이지요)

 

relay는 오로지 next 이벤트만 방출하는 특성을 가지고 있기 때문에 얘를 Driver 혹은 singal로 변환하는 경우에 에러에 대한 처리를 하지 않아도 되는것이지요!! (추가로 ControlProperty도 error를 가지지 않는 타입이기 때문에 asDriver() 로 변환이 가능하더라구요!!)

RxCocoa에 있다는 사실

그래서 PublishRelay - Signal, BehaviorRelay - Driver 라는 짝꿍이 생기게 됩니다!

각각의 특성에 맞게 Publish 혹은 Behavior Relay를 사용해야 알엑스를 잘쓴다고 할 수 있겠죠?

 

일반적으로는 share가 필요하지 않은 이벤트(버튼 클릭 등)에서는 signal을, 이외에 어떤 상태값(state)에는 driver를 사용한답니다.

 

5. 무지성 observeOn 남발하기

이번에도 driver, signal의 특성을 제대로 알지못해 발생했던 문제입니다(이정도면 driver를 그냥 모른게 아니신지)

UI 요소는 Driver가 국룰!! 만 알고 있던 저는 이를 사용할때 메인 스케줄러로 바꿔주려고 observeOn을 쓰려고 했는데 나오질 않자 이를 Observable로 변환하고 MainScheduler에서 observe하는 대참사를 보여줍니다(진짜 일 어떻게 한거냐 나...)

 

말이 나온김에 간단하게 subscribeOn과 observeOn에 대해 짚고 넘어가자면(제가 원래 이걸 몰랐거든요)

*참고로 이제는 observe(on: )이지만 라떼는 observeOn 이었답니다*

 

subscribeOn은 말그대로 구독을 하는 스케줄러, observeOn은 스트림에서 방출된 값을 observe 하는 스케줄러를 의미합니다. 이름을 보시면 정말 잘 지었구나 싶네요. 그래서 방출된 요소를 받아서 메인 스케줄러에서 UI 요소를 바꿔야한다고 하면 observeOn을 사용하는 것이지요. 사실 subscribe에 대한 스케줄러를 언제 지정해주는게 좋은지는 잘 모르겠습니다 ^^...

 

 


 

마무리

처음에 글을 구상할때만 해도 '나만의 꿀팁 대방출~~' 느낌이었는데 쓰고보니 '내가 멍청이였던 이유 대방출~~'이 되어버린 것 같네요 허허... 제가 Trait 별 특성에 대해 잘 모르고 RxSwift를 사용했었다는 아쉬움이 많이 남네요(이제 아니까 된건가 ^_^)

혹시나 '드라이버 시그널 그게 뭔데 무서워.. 나 그냥 서브젝트만 쓸래~~' 라고 하신다면 이참에 학습을 해보시는걸 추천드립니다! 코드가 훨씬~ 깔끔해지고 아렉수를 능수능란하게 쓰는데 한발짝 앞으로 갈 수 있답니다!