이번에는 MVVM에 관하여 글을 한번 쓰겠습니다.
MVVM이 무엇인지에 대하여는 워낙 많은 블로그에 소개되어 있어 생략하고 MVVM에 대한 저의 주관적인 관점을 중점적으로 설명 해드리겠습니다.
1. 바인딩 방법들
첫째는 바인딩 방법들에 관한 것 입니다.
MVVM이라고 하면 제 생각엔 ViewModel이 가장 중요한 컴포넌트인 것 같습니다.
ViewModel과 View를 바인딩 함에 있어 크게 3가지 정도의 분파가 있는 것 같습니다.
1. 첫번째 방법: Protocol 및 콜백 이용한 바인딩 방법
제 3의 라이브러리에 의지하지 않고 View와 ViewModel을 바인딩하는 방법입니다.
대충 요런식입니다. 필자는 이게 MVVM이다 라고 처음 봤을때 그럼 내가 해오던 MVP와 뭐가 다른건지 굉장한 혼돈이 왔었습니다.
제가 해오던 MVP 방식도 딱 저런 방식으로 Presenter와 View가 소통을 하고 있었습니다.
아마 대다수의 개발자 분들은 이런 형태를 선호하지 않을 거라 생각합니다.
일단 코드량도 많아지고 하나를 추가하거나 바꾸려고 하면 Protocol, View, ViewModel 3개를 전부 손을 봐야하기 때문입니다.
그리고 제 주관적인 관점에서는 Protocol에 생성하는 함수 만큼 View에 함수가 증가하게 되는데 이는 결국 Massive View Controller가 되는데에 어느정도 기여를 하지 않을까 생각합니다.
그리고 제일 중요한거 요즘은 신입개발자 분들도 필수로 하는 Rx를 하지 않으니 뭔가 간지가 나지 않습니다.
Rx가 MVVM에 필수인건 아니지만 편의성과 가독성 면에서는 이점이 있기 때문에 Rx를 MVVM과 많이 쓰는 것 같습니다.
2. 두번째 방법: Rx를 이용한 바인딩 방법(without ViewModelType)
많은 개발자분들이 애용하는 방법입니다.
Rx는 기본적으로 바인딩 자체를 위한 것이기 보단 stream을 활용하여 비동기적인 코드를 동기적으로 나타내는 것에 초점을 맞추어 나온 것으로 보이지만 데이터 바인딩의 간편함 때문에 MVVM의 필수요소로 여겨집니다.
이에 따라 Rx를 곁들인 MVVM은 RxMVVM이라고 불리기도 합니다.
보이는 것처럼 Protocol이 빠짐으로 인해서 코드량이 줄은것도 확인 할 수 있고 rx 바인딩을 통해서 bindView()함수 안에서만 처리하기 때문에 가독성이 올라 간것을 확인 할 수 있습니다.
하지만 이 형태 또한 단점이라면 단점일 수 있는 것이 아무래도 ViewModel 쪽에서 각종 stream을 위한 변수들이 접근 제한자가 public으로 되어 있고 또한 어떤것이 Input 인지 Output인지 한번에 봤을때 파악되지 않는 점이 있습니다.
3. 세번째 방법: Rx를 이용한 바인딩 방법(with ViewModelType)
세번째 방법은 2번째 방법에서 나온단점 즉 stream을 위한 변수들이 Public 접근 제한자로 되어 있는 것을 예방하기 위한 방법입니다.
ViewModel의 기본이 되는 Protocol을 선언하여 모든 ViewModel이 해당 Protocol을 채택하는 것입니다.
이 Protocol에는 간단한게
associatedType Input
associatedType Output
func transform(input : Input) -> Output
요렇게 3가지 요소가 있습니다.
보는 바와 같이 ViewModel로 들어오는 이벤트 Input
ViewModel의 결과를 표출하는 것을 Output
이것들의 stream을 만들고 연결해주는 transform(input : Input) -> Output 함수가 있습니다.
보시는 바와 같이 ViewController에서 해당 ViewModel에 대한 Input을 만들어 ViewModel에 보낸후 이것을 다시 출력값에 해당하는 Output을 받아와 바인딩을 진행합니다.
이렇게 함으로써 ViewModel의 변수를 직접적으로 참조하는 경우가 적어짐과 동시에 Input과 Output을 따로 두어 가독성 또한 증가하는 것을 볼 수 있습니다.
하나 유의해야 할것은 MVVM을 한다고 하면서 모든것을 stream으로 처리하려고 한다는 점입니다.
예를 들면 다음과 같습니다.
func transform(input: Input) -> Output {
let isConfirmEnabled = input.idInput
.map{ [weak self] value in
self?.idText = value
return value.count > 10
}
.asDriver()
input.onConfirmBtnClick
.drive(onNext : {
print("confirmbtn Clicked")
})
.disposed(by: disposeBag)
return Output(isConfirmEnabled: isConfirmEnabled)
}
//=================
private let isConfirmEnabled = PublishSubject<Bool>()
func transform(input: Input) -> Output {
input.idInput
.subscibe(onNext : { [weak self] value in
self?.checkisConfirmEnabled(text : value)
})
.disposed(by: disposeBag)
input.onConfirmBtnClick
.drive(onNext : {
print("confirmbtn Clicked")
})
.disposed(by: disposeBag)
return Output(isConfirmEnabled: isConfirmEnabled.asDriver())
}
private func checkisConfirmEnabled(text : String){
self.idText = text
isConfirmEnabled.onNext(text.count > 10)
}
과연 어떤것이 더 적합한 방법일까요?
그전에 다음 사진을 한번 보시겠습니다.
이 사진에서 보시면 UserInput에서 이벤트가 발생하는 시점으로부터 transform으로 거치고 비로소 다시 뷰 변경 발생!까지 한번도 stream을 끊지 않고 transform안에서는 오직 Operator만을 사용하여 stream을 뷰 변경 발생! 까지 끌고가서 disposeBag를 할당합니다.
그러면 위의 코드블러에서 언급한 두번째 방법처럼 ViewModel안에서 disposeBag에 할당하여 stream을 한번 끊는 것은 잘못된 방법인 걸까요?
저는 단호하게 아니라고 봅니다.
저 사진의 stream 관리는 어디까지나 하나의 예시일 뿐입니다. 오히려 저는 저렇게 했을 경우 Rx에 너무 매몰된다고 생각합니다.
저런식으로 하려면 API부터 사소한 모든 것까지 Rx를 사용해야 하는 경우가 생깁니다.
이렇게 되면 2019년에 소개된 swift 내장 라이브러리 Combine등을 도입하려고 할때 과도한 기술부채가 생긴다고 생각합니다.
이렇듯 Rx를 씀에 있어 모든 것을 stream으로 관리할 필요는 없다는 것입니다. 적재적소 과하지 않게 필요한 곳에만 Rx를 쓰는게 오히려 저는 더 좋다고 생각합니다.
결론
MVVM에 있어서 데이터를 바인딩 하는 방법을 3가지로 간단하게 알려드렸습니다.
물론 저 3가지 외에 회사별로 개인별로 조금씩 자기 입맛에 맞게끔 변형하여 사용하는 것을 생각하면 훨씬더 많은 방법이 있습니다.
이렇듯 MVVM은 많은 개발자의 사랑을 받고 도입되어 쓰이고 있지만 통일성이 없다는게 MVVM을 처음 접하는 분들의 가장 큰 문제점이지 않을까 싶습니다.
저또한 처음 MVVM을 접할때 굉장히 고민이 많았습니다. 제 경우에는 창업을 하면서 처음부터 iOS 개발을 혼자 진행해 왔었고
처음 배운 소프트웨어 디자인 패턴도 먼저 개발을 시작 AOS개발자에게 어깨건너로 배운 MVP 패턴 이었습니다.
시간이 지남에 따라 MVP 패턴이 iOS와 맞지 않다고 생각하였고 MVVM으로 넘어가기 위하여 공부를 했습니다.
이 과정에서 저는 MVVM을 뭔가 굉장히 세련되고 신선한 아키텍쳐 패턴이라고 생각하고 있었습니다. 하지만 찾아보면 찾아볼수록 이 사람 저 사람 이야기 하는 것이 달랐고 어떻게 보면 그냥 MVP에 Rx를 써서 데이터 바인딩 하면 MVVM이 아니야?라고 생각하며 RxMVP를 사용했었습니다. 그래서 그당시에도 심지어 지금까지도 왜 MVVM에서 ViewModel이 MVP의 Presenter보다 더 재활용하기 쉬운것인가?에 대한 의문을 가지고 있습니다.
그러다가 Input Output이 제대로 분리되어 있지 않다는 것을 몸소 체험하고 최종적으로는 Protocol Input & Output의 형식을 도입하여
사용했습니다.
바인딩 방법외에도 저는 MVVM에 대한 근본적인 의문들을 아직도 많이 가지고 있습니다. API는 어디에서 부르는 것인가 ViewModel인가 Model인가? View의 전환은 ViewController의 몫인가 ViewModel의 몫인가? ViewModel은 struct인가 class인가 등등
다음글에 대해서는 MVVM에 대한 다른 부분에 관하여 글을 써보겠습니다.
'iOS' 카테고리의 다른 글
MVVM(3) - Coordinator (0) | 2022.12.23 |
---|---|
MVVM (2) (0) | 2022.12.19 |
KeyChain vs UserDefaults(2) (0) | 2022.11.22 |
KeyChain vs UserDefaults(1) (0) | 2022.11.17 |
some 과 any 그리고 Any?(2) (0) | 2022.10.26 |