소프트웨어 개발에서 MVC(Model-View-Controller)와 MVVM(Model-View-ViewModel) 패턴은 UI를 구현할 때 자주 사용되는 디자인 패턴입니다. 두 패턴 모두 유지보수성과 역할 분리를 목표로 하지만, UI 업데이트 방식에서 중요한 차이가 있습니다. 이 글에서는 MVC와 MVVM의 주요 차이점과, 특히 UI 업데이트를 수동으로 처리하느냐, 자동으로 처리하느냐의 차이를 중심으로 살펴보겠습니다.
MVC 패턴은 Model, View, Controller로 구성됩니다. Controller는 View와 Model 사이에서 중재자 역할을 하며, UI 이벤트 처리와 데이터 변경을 모두 책임집니다. Controller는 사용자의 상호작용에 따라 Model을 업데이트하고, 그 결과를 View에 수동으로 반영해야 합니다.
MVC에서 ViewController는 다음과 같은 역할만 수행하는 것이 이상적입니다.
viewDidLoad()
, viewWillAppear()
, viewDidAppear()
등의 생명주기 메서드에서 UI를 초기화 및 갱신반대로, ViewController에서 하면 안되는 역할은 다음과 같습니다.
// Model
struct User {
var name: String
var age: Int
}
// View (UIView)
class UserView: UIView {
let nameLabel = UILabel()
let ageLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
nameLabel.translatesAutoresizingMaskIntoConstraints = false
ageLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(nameLabel)
addSubview(ageLabel)
// Layout constraints
NSLayoutConstraint.activate([
nameLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
nameLabel.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -20),
ageLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
ageLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 10)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateView(with user: User) {
nameLabel.text = "Name: \\(user.name)"
ageLabel.text = "Age: \\(user.age)"
}
}
// Controller (UIViewController)
class UserController: UIViewController {
private var user: User!
private var userView: UserView!
override func viewDidLoad() {
super.viewDidLoad()
userView = UserView(frame: view.frame)
view.addSubview(userView)
// Model에서 데이터 가져오기
user = User(name: "Alice", age: 30)
// View를 수동으로 업데이트
userView.updateView(with: user)
}
// 사용자 데이터를 업데이트하고 다시 View를 갱신하는 함수
func updateUser(name: String, age: Int) {
user.name = name
user.age = age
// 수동으로 View 업데이트
userView.updateView(with: user)
}
}
따라서 ViewController가 UI 및 이벤트 로직 외의 역할을 가지지 않도록 구조를 설계하는 것이 중요합니다. 만약, UI, 네트워크, 데이터 처리 등을 모두 한 곳에서 하면 뷰 컨트롤러의 역할이 과도해지고 유지보수가 어려워집니다.
이러한 경우엔 MVVM, MVP, VIPER 같은 디자인 패턴을 도입하여 역할을 분리해야 합니다. 이 글에서는 MVVM에 대해서만 다뤄 보겠습니다.
MVVM 패턴은 Model, View, ViewModel로 구성되며, 핵심 차이점은 데이터 바인딩을 통해 자동으로 UI가 업데이트된다는 것입니다. ViewModel은 View와 Model 사이에서 UI 상태와 데이터 처리를 담당하지만, UI 업데이트는 데이터 바인딩을 통해 자동으로 이루어집니다.
MVVM에서 ViewModel은 View와 Model 사이의 중간 계층으로서 다음과 같은 역할을 수행합니다.
비지니스 로직 처리
ViewModel은 View에서 받은 사용자 입력을 기반으로 비지니스 로직을 실행합니다. 예를 들어, 사용자가 버튼을 눌렀을 때 특정 데이터를 가공하거나, 상태를 변경하는 등의 역할을 수행합니다.
네트워크 요청 수행(Service 계층 호출)
ViewModel은 네트워크 요청을 수행하지만, 직접 네트워크 요청을 수행하지 않고 Service 계층을 호출하여 데이터를 가져옵니다.
즉, API 요청을 ViewModel에서 실행하는 것이 아니라 Service
또는 Repository
에서 수행하고, ViewModel은 그 결과를 받아 View에 전달하는 역할을 합니다.
func fetchUserData() {
userService.fetchUser { [weak self] user in
DispatchQueue.main.async {
self?.user = user
}
}
}
데이터 가공 및 변환
ViewModel은 View에서 바로 사용할 수 있도록 데이터를 변환하는 역할을 합니다.
하지만, 데이터 변환이 복잡한 경우 Model에서 처리하는 것이 더 적절합니다.
View와의 데이터 바인딩
View에서 사용할 데이터를 @Published
또는 ObservableObject
를 통해 바인딩 합니다.
view는 ViewModel의 데이터를 구독하고, 데이터가 변경되면 UI를 자동으로 업데이트 합니다.
ViewModel은 UI를 직접 업데이트 하지 않음
UIkit의 ViewController처럼 UI를 직접 견경하는 코드가 포함되면 안 됩니다.
UI 업데이트는 View에서 ViewModel의 데이터를 구독하여 자동으로 반영하는 방식을 처리해야 합니다.
MVVM에서 ViewModel이 View에 대해 알 필요가 없고, UI 업데이트가 자동으로 이루어지기 때문에 MVC의 Controller보다 유지보수성이 높습니다. View와 ViewModel은 느슨하게 결합되어 있어, UI의 변경 사항이나 데이터 업데이트가 자동으로 처리됩니다.
// Model
struct User {
var name: String
var age: Int
}
// ViewModel
class UserViewModel: ObservableObject {
@Published var user: User
init(user: User) {
self.user = user
}
// ViewModel이 데이터를 업데이트하는 메서드
func updateUser(name: String, age: Int) {
user.name = name
user.age = age
}
}
// View (SwiftUI View)
struct UserView: View {
@ObservedObject var viewModel: UserViewModel
var body: some View {
VStack {
Text("Name: \\(viewModel.user.name)")
Text("Age: \\(viewModel.user.age)")
Button(action: {
// ViewModel을 통해 데이터를 업데이트
viewModel.updateUser(name: "Bob", age: 25)
}) {
Text("Update User")
}
}
}
}
// SwiftUI에서 ContentView
struct ContentView: View {
var body: some View {
UserView(viewModel: UserViewModel(user: User(name: "Alice", age: 30)))
}
}
MVC와 MVVM의 가장 큰 차이점은 UI 업데이트 방식을 수동으로 처리하느냐, 자동으로 처리하느냐에 있습니다.
MVVM에서 데이터 바인딩은 UI 업데이트의 자동화를 가능하게 하는 핵심 요소입니다. 이 자동화 덕분에 View는 ViewModel의 상태만 구독하며, UI는 항상 최신 상태를 유지합니다. 반면에 MVC는 View와 Controller 간의 긴밀한 상호작용이 필요하여, 변화가 있을 때마다 Controller에서 직접 View를 조작해야 합니다.
결국, MVVM에서의 데이터 바인딩은 의존성을 낮추고, UI 로직을 더 쉽게 유지보수할 수 있게 도와줍니다. MVC에서는 이러한 자동화가 없기 때문에, View와 Controller가 더 강하게 결합될 수밖에 없습니다.
MVC와 MVVM의 가장 큰 차이점은 UI 업데이트를 수동으로 처리하느냐, 아니면 데이터 바인딩을 통해 자동으로 처리하느냐입니다. MVC는 Controller가 모든 UI 로직과 업데이트를 수동으로 처리하지만, MVVM은 ViewModel과 데이터 바인딩을 통해 UI 업데이트를 자동화하여 유지보수성을 높이고 의존성을 낮춥니다.
MVVM 패턴을 사용할 때 데이터 바인딩은 필수 요소로, 이를 통해 UI와 비즈니스 로직의 명확한 분리가 가능해집니다. 이러한 자동화된 업데이트 덕분에 복잡한 UI 로직을 더 간결하게 유지할 수 있으며, 대규모 애플리케이션에서 더 나은 아키텍처를 제공합니다.
이미지 출처
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html
https://velog.io/@kyeun95/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-MVVM-%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80