
MVVM을 사용하면서 가장 힘들었던 부분은 모두가 조금씩은 다른 각자의 형태로 MVVM을 사용하고 있다는 점이었습니다.
어느정도 틀이 잡혀 있는 Input/Output Transform 등 MVVM을 다양한 방식으로 시도했었지만 그 틀 안에서도 ViewModel과 ViewController를 어떻게 만들 것인지에 대한 고민은 끝이 없었던 것 같아요.
"Input으로 이벤트가 들어와서 Output으로 결과를 View로 반환한다."라는 개념 자체는 단순했지만,
Delegate 처리를 위한 값은 어떻게 저장할 것인지, 그 결과가 어떻게 생겨야 하는지, transform의 input은 View에서 주입되어야 하는지 ViewModel에서 주입되어야 하는지, ViewModel 간의 데이터 바인딩은 어떤 방식으로 이루어져야 하는지 등, 조그만한 고민들을 끝도 없이 해야 했습니다...
또 transform(Input) -> Ouput 메소드에서 모든 event 처리 작업을 해주고 있다보니 transform이라는 단일 메소드가 너무 커지는 불편함도 있었어요.
transform을 관심사에 따라 나눠서 메소드를 만들어 해결하는 방법도 있겠지만, 단순히 메소드를 나눈다의 개념으로 메소드의 복잡성을 줄이기는 쉽지 않아 보였습니다.

별 기능이 없어도 너무 길었던 transform 메소드...
1. 왜 ReactorKit이 필요할까?
이런 말이 있죠!
<잘못된 선택보다 올바른 선택을 하는게 더 쉬운 구조가 좋은 아키텍처다.>
Sourcery 개발자로부터 배우는 모바일 아키텍처와 개발자 경험 · Soojin Ro
iOS 개발자라면 한번은 들어봤거나 써봤을 오픈소스 툴 Sourcery를 만든 Krzysztof Zabłocki와 팟캐스트 녹음을 했는데 작년 엉클밥을 봤을 때와 비슷한 큰 자극와 영감을 얻었습니다. Krzysztof는 좋은 앱
soojin.ro
저도 개발을 하다 보면... 특히 급하게 개발을 할 때, 돌아보면 왜 이렇게 했지? 싶은 코드들이 많더라구요.
마치 의자에 앉아있다보면 아무리 좋은 의자여도 그 위에서 다리꼬고 이상하게 기대서 허리가 망가지는 것처럼, 자유도가 높은 아키텍처에서는 끊임없이 생각하지 않으면 어느 순간 잘못된 코드를 만들고 있는 제 자신을 발견할 때가 많습니다...
이런 측면에서 너무도 높은 자유도를 가지고 있는 MVVM은 잘못된 선택을 하기 너무 쉬운 구조가 아닐까요?
또 나와 다른 방식으로 쓰고 있는 다른 팀원의 ViewModel을 한 눈에 이해하기도 쉽지 않을 것 같더라고요!!
Reactorkit은 이런 문제에 도움을 줄 수 있는 좋은 도구라고 생각해요.
데이터 흐름의 단방향을 강제하고 있고, 각각의 단계에서 해야 하는 일들이 명확하게 정의되어 있기 떄문이죠.
그리고 Event를 Input (Action) 으로 받고, State를 Outptut으로 내보낸다는 점에서, 상태 중심의 패턴을 만들 수 있도록 도와줍니다.

2. ReactorKit을 사용하면서 얻은 것들
직접 프로젝트에 ReactorKit을 사용해보니 장점이 분명히 있었습니다.
1. 코드의 예측 가능성 증가, 가독성 증가
먼저 Action과 State를 통해 View와 ViewModel(Reactor)이 통신하기 때문에,
View에서 일어나는 이벤트에 대해서는 Reactor의 Action,
View에서 변화하는 값을 보기 위해서는 State 만 확인하면 되어서 해당 컴포넌트를 파악하는게 매우 편했습니다.
그리고 관련 로직에서 문제가 생기는 부분은 mutate 메소드를 확인하면 되고,
해당 component 외부에서 일어나는 일에 대해서는 transform 부분을 확인하면 되기 떄문에 마찬가지로 내부 로직과 외부와의 연관성을 파악하는 것에도 유용했습니다.
무엇보다 제가 만든 코드 외에 다른 팀원이 만든 코드도 같은 구조로 이루어져 있을 것이라는 예상을 하고 접근하기 때문에 이해하는 속도가 매우 빨랐습니다.
2. 단방향 데이터 플로우
Reactorkit의 가장 핵심적인 기능이죠!
사실 이 부분은 MVVM의 Input/Ouput 패턴을 통해서 얻을 수 있는 부분이지만, 기존의 mvvm 패턴에서는 신경써서 이뤄내야 하는 단방향 패을 Reactorkit을 사용하면 쉽게 이뤄낼 수 있었습니다.
각각의 역할에 충실한 로직만 짜주면 그게 단방향이 되니까요ㅎㅎ
2. 쪼개고 이름 붙이고... 확장에 용이하지만 더욱 단순해진 로직
각각의 단계에 이름을 붙일 수 있습니다. 동작을 추가할 때는 enum에 요소를 추가하고, 로직을 구현해주기만 하면 됩니다.
또한 바람직하지는 않겠지만 어쩔 수 없이 Reactor의 규모가 커지는 경우에는 action, mutation, state등을 여러개로 분리하면 됩니다.
이러한 일련의 과정을 거치다 보면 코드가 매우 단순해집니다.
3. Test에 용이합니다.
잘 짜여진 ViewModel처럼, Reactor도 마찬가지로 유닛 테스트에 용이합니다.
테스트를 위한 내장 기능이 있기 때문에 간단하게 테스트 할 수 있고,
애초에 설계 자체가 Action이 들어와서 State가 나가는 형태를 지킬 수 밖에 없기 때문에, Action과 State만 염두하면 어렵지 않게 Test를 만들 수 있더라구요!
4. 필요한 부분에만 사용할 수 있습니다.
모든 ViewModel이 다 Reactor가 될 필요가 없습니다. 정말 간단한 View는 Reactor를 사용하지 않고 단순한 MVVM으로 구성해도 되기 때문에 오버 엔지니어링을 충분히 막을 수 있죠!!
5. 그리고 이 모든 장점을 작은 러닝커브로 이뤄낼 수 있습니다.
단순히 사용하기 위해서는 ReactorKit은 정말 쉽습니다.
ReactorKit에 RX 요소가 거의 들어가지 않아도 될 만큼 자동화 되어있어서, Input 값을 내가 필요로 하는 Output으로 바꾸는 로직만 신경써주면 됩니다.
또 ReactorKit의 코드 자체가 그렇게 복잡하지 않기 때문에 조금만 내부를 뜯어봐도 이게 어떻게 동작하고 있는지 금방 이해할 수 있습니다.
간단한 코드로 이렇게 큰 영향력을 줄 수 있구나 놀라게 될거에요 createStateStrem 메소드만 자세히 살펴봐도 ReactorKit이 어떻게 구성되어 있는지 간단하게 이해할 수 있습니다.
하지만, 저에게는 여전히 불편한 점도 존재하는데요.. 이 부분에서도 이야기를 해보려고 합니다.
3. 여전히 고민하고 있는 부분
1. ViewModel이 View의 구성 요소를 암시하고 있는 문제
ReactorKit 뿐만 아니라 MVVM 대부분에 해당되는 고민일 수도 있을 것 같습니다.
모두가 아시다 싶이, ViewController가 ViewModel에 의존하고 있는데요, 즉, View의 구성요소가 바뀐다고 해서 ViewModel의 로직이 바뀔 필요는 없어야 합니다.
제 코드를 보시면, ViewModel (Reactor)가 View의 UI를 알고 있는 것 처럼 보입니다.
enum Action {
case didTapSegmentControl(Int)
case didSlidePage(Int)
case didTapEditModeButton
case didTapStyleModeButton
case didTapDeleteButton
}
didTapEditModeButton 이라는 네이밍만 봐도, ViewModel은 View에 Button이 있을 거라는 것을 암시하고 있죠.
만약에 eidtMode가 이제 View에서 button이 아니라 Slider를 통해서 바뀐다고 해봅시다.
그럼 didSlideEditMode 로 네이밍이 변경되어야 할까요?
참고할 수 있는 대부분의 코드에서 Action이 이처럼 View의 요소를 암시하고 있는 경우가 많은데, 이는 Action이라는 네이밍 자체의 문제일 수도 있다고 생각해요.
Action은 View에서 바라보는 행위이기 때문이죠. 즉, View의 관점을 ViewModel에서 취하고 있는 문제로 보여요.
하지만, ViewModel에서 바라보는 행위라면 Action이 아니라 Task라는 이름이 되는게 좋지 않을까? 하는 생각을 계속 하고 있습니다.
View가 단순히 ViewModel에게 일을 시키는 개념으로 가야하지 않을까요?
Task의 개념으로 Action을 수정한다면, 다음과 같을 수 있을 것 같아요
enum Task {
case shouldChangePageIndex(Int)
case shouldChangeEditMode
case shouldChangeStyleMode
case shouldDeleteItems
}
하지만.. 이런 경우에 mutate와 너무 역할이 겹치는 것 같다는 생각도 드네용
이 부분은 추후에 더 업데이트 해보도록 하겠습니다.
2. ViewController Life Cycle과의 괴리
대표적으로 View의 Reactor 간의 초기화 시점 차이입니다.
Reactor는 ViewController를 만들 때 외부에서 주입 받거나, viewDidLoad 단계에서 만들어서 넣어주게 되는데요.
아시다 싶이, Reactor의 CreateStateStream 메소드는 Rx의 replay와 connect를 사용하고 있기 때문에, CreateStateStream가 호출되는 State의 생성 시점에 State를 구독하는 모든 스트림에 변경사항을 알리게 됩니다.
private var _state: Observable<State> {
if self.isStubEnabled {
return self.stub.state.asObservable()
} else {
return MapTables.state.forceCastedValue(forKey: self, default: self.createStateStream())
}
}
let transformedState = self.transform(state: state)
.do(onNext: { [weak self] state in
self?.currentState = state
})
.replay(1)
transformedState.connect().disposed(by: self.disposeBag)
return transformedState
여기서 문제는, State가 생성 될 때, 아직 View가 준비가 안되어 있을 가능성이 크다는 것인데요.
예를 들어, 어떤 State 값에 따라, 기존 frame을 참조해서 다른 view를 그려야 한다고 했을 때, reactor가 생성되는 시점이 ViewController가 init되는 시점이라면, 해당 view가 아직 그려지지 않아서 아직 frame이 완성되지 않은 상태에서 참조될 수 있습니다.
이러한 문제점들은 개발자 단위에서 신경써서 해결할 수 있는 부분일 수도 있을 것 같은데요.
ReactorKit은 너무도 좋은 도구이지만, 역시 무작정 사용하기보다는 더 좋은 방법을 고민하고 찾아가면서 사용하면 더 좋은 도구가 될 수 있지 않을까 생각합니다!
'Swift 아키텍처' 카테고리의 다른 글
[ReactorKit] (2) Reactorkit 내부 구조를 뜯어보고 사용하자 (0) | 2024.03.03 |
---|---|
[RIBs] RIBs 분석 (0) | 2024.01.08 |
[MVC] iOS의 MVC 아키텍처에 대해서 (0) | 2022.09.06 |
[Redux] Redux를 이용한 상태관리 후기 (with SwiftUI) (2) | 2022.05.15 |
[Redux] SwiftUI에서 Redux 아키텍처 적용해보기 - 첫인상 (0) | 2022.04.18 |