MVVM
은 MVC
에서 파생된 모델로 Model-View-ViewModel의 약자이다.
2005년 Microsoft에서 View가 디자이너의 책임인 개발플랫폼의 진화에 맞춰 MVC를 변형시켜 만들어진 패턴이라고 한다..
MVVM
구조
Model
: 모델은 데이터 모델뿐만 아니라 비즈니스 로직을 포함한 앱의 도메인 모델(추상화된 객체 모델)을 말한다
View
: 애플리케이션의 유저 인터페이스를 나타내며 보여지는 동작(애니메이션 등 렌더링)만을 수행하는 객체로 모델을 알지 못해 데이터를 가지지도 조작하지도 못한다!
(오직 뷰모델과의 데이터 바인딩을 통해 동작)
ViewModel
: View
가 데이터 바인딩을 통해 사용하는 속성과 명령을 구현하고 노출한다. 상태 변경이 발생하면 알림 이벤트 등을 통해 View
에 알린다
즉, View
와 Model
은 서로 통신하지 않으며 View
는 사용하는 속성과 메서드를 가지고 있는 View Model
과만 통신하고, View Model
은 Model
과만 통신하며 View
와 Model
사이의 통신을 중개한다.
디자이너와 협업이 용이하다: View
와 프로그래밍 로직이 분리되어 있어 디자이너와 개발자는 서로 다른 구성요소(디자인: View
, 개발: ViewModel
, Model
)에 대해 동시에 작업할 수 있다!
UI 테스트가 용이하다: 마찬가지로 View
가 완전히 독립적이므로 UI와 비즈니스 로직 각각 테스트할 수 있다.
유지 보수, 기능 추가가 용이하다: 애플리케이션의 구성요소를 작은 단위까지 모듈화해 코드가 직관적이고 이해하기 쉽다.
-> 유지 보수가 용이하며 새로운 기능을 어디에 구현하고 기존 코드와 어떻게 연결되는지 알기 쉽다.
비용이 크다: 어플리케이션을 구현하는데 더 많은 작업을 필요로 해 모델을 설계하고 개발하는데 시간이 오래걸리며 복잡해진다.
디버깅이 어렵다: 명령형 코드보다 선언형 코드가 더 디버깅하기 어렵다.. View
와 View Model
의 느슨한 결합은 비즈니스 로직과 UI간의 연결과 흐름을 파악하고 디버깅하기엔 더 어려울 수 있다.
UIkit
을 사용한 MVVM
구조는 ViewController
의 이름만 바꿔 ViewModel
로 만들어 여전히 Massive하며 데이터 바인딩 과정이 더 복잡하게 만든다고도 한다..!
MVVM
의 핵심인 View
와 View Model
간의 데이터 바인딩을 하는 방법으로는 KVO
, Delegate
패턴, RxSwift
와 Combine
같은 반응형 프레임워크를 사용하는 방법들이 있겠다...
추후 UIkit
의 MVC
구조로 구현된 프로젝트를 MVVM
구조로 리팩토링하는 작업을 해볼거지만(포스팅 예정 ㅠ) MVVM
의 구조만 구현해보기 위해 SwiftUI
와 속성 래퍼를 사용해 간단하게 구현해보자..
import Foundation
struct Person {
var birthDay: Date
var age: Int {
return Calendar.current.dateComponents([.year], from: birthDay, to: Date()).year!
}
}
import SwiftUI
struct ContentView: View {
@StateObject var viewModel = ContentViewModel()
var body: some View {
VStack {
Spacer()
Text("나이 계산기")
.fontWeight(.bold)
.padding()
Spacer()
DatePicker("birthDay",
selection: $viewModel.birthDay,
displayedComponents: [.date])
.datePickerStyle(.graphical)
.labelsHidden()
.padding()
Text(viewModel.getAge())
Spacer()
}
Spacer()
}
}
#Preview {
ContentView()
}
import Foundation
class ContentViewModel: ObservableObject {
@Published var birthDay = Date()
func getAge() -> String {
let person = Person(birthDay: birthDay)
return "만 \(person.age)세"
}
}
p.s. 작성하다보니 Date
타입에 대해서 정리를 한번 할 필요가 있는거 같다..(추후 포스팅!)
간단하게 작성하려 했지만 작성하다보니 점점 길어졌는데, computed property
로 구현한 부분을 Model
자체에 Service
를 추가할 수도 있을거 같다.단순히 Age
라는 모델이 제공할 기능인지를 명확하게 구분하기 힘들었다..
View
가 Model
을 알지 못하도록 수정,,