🗣 이 글은 MVC 패턴은 익숙하지만 MVVM은 처음 접한 분들을 위한 글입니다. 잘못된 부분이나 애매한 부분에 대해서 댓글로 피드백 주시면 감사하겠습니다🙌🏻 또한 댓글을 통한 토론도 환영입니다!
MVVM이란?
(출처: Stanford 강의, 위 강의는 SwiftUI 기반으로 MVVM을 설명하고 있습니다. 이 글은 UIKit을 기반으로 작성되었습니다.)
위 이미지에서 Works in concert with the concept of “reactive” user-interfaces. 이 부분이 중요한 포인트입니다.
MVVM을 정리해보자면 ❶ UI 로직과 비즈니스 로직을 분리하고, ❷ 리액티브한 UI 컨셉과 함께 협력하여 작동하는 디자인 패턴(아키텍쳐 패턴) 라고 할 수 있을 것 같습니다.
제가 MVVM에 대해서 찾아보게 된 계기도 바로 이 때문인데요. 그럼 본격적으로 MVVM에 대해서 좀 더 알아봅시다 🙌🏻
MVC에서 MVVM을 찾게 된 과정
입력/터치 이벤트가 발생했을 때 Model의 State가 변경되고 View가 State의 변화를 감지하고 있다가 변경되면 State에 맞게 View를 업데이트 하도록 구현 하고 싶었고, KVO/Notification을 이용해서 구현했습니다.
그런데 View와 Model이 자신들의 역할에 충실한 것(View는 화면을 그리는 것, Model은 앱 데이터, 비즈니스 로직)을 우선시하여 코드를 짰더니 View Controller가 점점 무거워졌습니다.
또한 정상적으로 동작하는지 확인하기 위해 매번 시뮬레이터를 실행하면서 결과를 확인했었는데요. 화면이 많아지고 기능이 많아지다보니 이 작업이 상당히 비효율적이라고 느꼈었습니다.
그래서 테스트 코드를 작성하기 시작했습니다. 그런데 이 때 View나 ViewController 인스턴스를 생성해야 하고 생각보다 제약과 불편점이 많았습니다.
이를 테스트하기 위해서는 View와 비즈니스 로직을 분리하는 일이 필요하다는 걸 느꼈습니다.
그러던 중 1-5번 고민을 해결할 수 있는 좀 더 좋은 방법을 찾아가 발견한 것이 MVVM 패턴입니다.
MVVM의 규칙들
💡 MVVM에 대해서 공부하면서 MVVM이 뭔지, 어떻게 구현하는지 알아보기 위해 많은 예제와 자료를 봤는데요. 공부하면서 든 생각은 “MVVM에 정형화된 형식같은 건 없다. 다만 공통적으로 적용되는 규칙들이 있다.” 입니다.
MVVM에 대한 설명을 모아보면 이렇습니다. (참고로, 위 영상 Stanford 강의 에서 MVVM에 대한 강의를 보면 MVVM에 대한 컨셉을 이해하기 좋습니다. 저의 경우 Stanford 강의에 나온 이미지와 함께 보면서 더 이해하기 쉬웠기 때문에 이미지도 함께 첨부하겠습니다. )
1. MVVM은 ViewModel을 통해 UI 로직과 비즈니스 로직을 분리했다. 2. MVVM은 MVC와 달리 ViewModel이 있다. 3. ViewModel은 Model을 참조한다(반대는 X). 4. View 없이 테스트가 가능하다. 5. ViewModel은 View input으로부터 Model을 업데이트한다. 6. ViewModel은 Model이 변경되면 View에 반영한다. (Model output으로부터 View를 업데이트한다.) 7. ViewModel은 View에 직접적으로 이야기하지 않는다. 무언가 바뀌었다고 발표(publish) 한다. 9. 모든 UI 컨트롤의 상태를 알려주는 프로퍼티들을 포함한다.
Model
1. UI에 독립적이다. 2. SwiftUI나 UIKit을 import 하지 않는다. 3. App이 하는 일에 대한 데이터와 로직을 캡슐화하려고 한다. 4. Model이 변경됐을 때 ViewModel에게 알린다.
간단한 MVVM 예제
MVVM은 주로 RxSwift, RxCocoa, SwiftUI, Combine과 함께 사용합니다.
그렇다면 RxSwift, RxCocoa, SwiftUI, Combine을 알아야만 MVVM을 사용할 수 있을까요? 그렇지 않습니다.
func promptForLocation(_:)에서 changeLocation(to:)메소드를 호출한다. 이 메소드에서 WeatherViewMode 클래스가 가지고 있는 프로퍼티 locationName을 업데이트한다. → self.locationName.value = location.name 👊🏻이 부분
locationName 은 Observable 타입이다.
Observable 은 init의 매개 변수로 value를 주입하고, Observable 에서는 제네릭을 사용하고 있기 때문에 이 때 value의 타입과 같게 타입이 결정된다. 따라서 여기서는 WeatherViewModel 클래스에서 String으로 초기화 하였기에 String 타입이다. → let locationName = Observable("Loading...") 부분
value가 변경되면(didSet) listener?(value) 즉, 클로저를 실행한다.
listener?(value) 는 Listener 타입이다. → Listener = (T) -> Void
그럼 여기서 실행할 클로저는 무엇이냐면, WeatherViewController 클래스에서 View와 ViewModel을 바인드 해줬던 👍🏻이 부분이다. →
클로저에 담겨 온 location.name value를 cityLabel.text로 설정한다.
이 동작 흐름이 이해가 되셨다면 거의 다 오신겁니다!👍🏻 이 과정을 이해했다면 나머지 부분은 MVC와 비슷합니다.
더 용이해진 테스트
자 이렇게 되면 View 없이 ViewModel만 가지고 테스트하기 훨씬 용이합니다. 시뮬레이터를 실행하거나 View나 ViewController 인스턴스를 생성해서 장소 이름을 변경하면서 cityLabel의 text가 장소 이름에 맞게 제대로 업데이트 되는지 확인하는 것 보다 정상적으로 동작하는지 확인하기 위해 아래와 같이 ViewModel에서 장소 이름을 설정하면 올바른 locationName을 가지고 오는지 ViewModel만 가지고 확인할 수 있으니까요. 🎉
MVVM 맛보기 정도의 글이라고 생각해주시면 좋을 것 같습니다. MVC에서 단점이라고 느꼈던 점들(MVC에서 MVVM을 찾게 된 과정)에 대해서 MVVM에서는 이렇게 할 수 있구나 느낀 점이 있는데 혹시 저처럼 MVVM이 처음이신 분들에게 공유하면 좋을 것 같아서 정리해봤습니다. 저도 MVVM에 대해서 잘 알려면 아직 멀었지만 앞으로 MVVM 관련해서 더 공부하면서 점점 더 나은 코드와 구조, 성능의 앱을 만들기 위해서 고민할 수 있을 것 같아 많이 기대가 됩니다.👻