티스토리 뷰
iOS 개발을 하다 보면 ViewController가 너무 비대해지는 문제를 자주 마주하게 된다.
네트워크 요청, UI 로직, 데이터 변환 등이 한 곳에 몰리면서 코드가 복잡해지고 유지보수가 어려워지는 문제가 발생한다.
이 문제를 해결하는 방법 중 하나가 바로 MVVM(Model-View-ViewModel) 패턴이다!
MVVM을 활용하면 코드를 더 구조적으로 분리할 수 있고, 테스트가 쉬워지며, UI 로직을 깔끔하게 정리할 수 있다.
이번 포스팅에서는 MVVM 패턴의 개념, 역할별 구조, 그리고 적용 방법까지 정리해보자.
1️⃣ MVVM이란?
✅ MVVM의 기본 개념
MVVM은 Model-View-ViewModel의 약자로, iOS 개발에서 ViewController의 책임을 줄이고 역할을 분리하기 위해 사용되는 패턴이다.
| 컴포넌트 |
역할 |
| Model | 데이터 및 비즈니스 로직을 담당 |
| View | 사용자 인터페이스(UI) 및 이벤트 처리 |
| ViewModel | View와 Model 사이에서 데이터 변환 및 UI 업데이트를 처리 |
📌 MVVM의 핵심 목표:
- ViewController의 역할을 최소화하고, UI 로직을 ViewModel에서 처리
- UI(View)와 비즈니스 로직(Model)을 분리하여 코드의 재사용성을 높임
- 데이터 바인딩(Data Binding) 기술을 활용해 View와 ViewModel을 쉽게 연결
이제 MVVM이 왜 필요한지 예제와 함께 살펴보자!
2️⃣ MVVM이 필요한 이유 (MVC의 문제점)
기존 MVC 패턴의 문제점
전통적인 iOS 개발에서는 MVC(Model-View-Controller) 패턴을 사용한다.
하지만 MVC의 가장 큰 문제는 ViewController가 너무 많은 역할을 담당하게 된다는 점이다.
이를 흔히 "Massive ViewController" 문제라고 한다.
class UserViewController: UIViewController {
let networkService = NetworkService()
var users: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
fetchUsers()
}
func fetchUsers() {
networkService.getUsers { [weak self] users in
self?.users = users
self?.updateUI()
}
}
func updateUI() {
// UI 업데이트 코드
}
}
문제점:
- ViewController가 네트워크 요청, 데이터 변환, UI 업데이트까지 모두 담당
- 테스트가 어렵다 → ViewController를 유닛 테스트하려면 네트워크 로직도 함께 테스트해야 함
- 재사용성이 낮다 → 다른 화면에서도 유사한 로직이 필요하면 중복 코드가 발생
✅ 이 문제를 해결하는 방법이 바로 MVVM 패턴이다!
3️⃣ MVVM의 역할과 구조
MVVM을 적용하면 코드 구조가 이렇게 변경된다.
1️⃣ Model → 데이터를 나타내는 구조체 또는 클래스로, 네트워크나 데이터베이스에서 가져온 데이터를 저장
2️⃣ ViewModel → Model을 가공하여 View에 전달, UI 로직을 처리
3️⃣ View(ViewController) → ViewModel에서 제공하는 데이터를 UI에 표시
[ViewController] <------> [ViewModel] <------> [Model]
4️⃣ MVVM 적용하기
✅ 1. Model 만들기
Model은 데이터 구조를 정의하고, 네트워크나 데이터베이스에서 가져온 데이터를 저장하는 역할을 한다.
struct User {
let id: Int
let name: String
}
✅ 2. ViewModel 만들기
ViewModel은 데이터를 가공하고 UI 로직을 처리하는 역할을 한다.
View에서 직접 Model을 다루지 않고, ViewModel을 통해 필요한 데이터를 전달받도록 한다.
class UserViewModel {
private let networkService = NetworkService()
private(set) var users: [User] = []
var onUsersUpdated: (() -> Void)?
func fetchUsers() {
networkService.getUsers { [weak self] users in
self?.users = users
self?.onUsersUpdated?()
}
}
}
📌 핵심 포인트
✅ ViewModel은 Model을 직접 노출하지 않고, 필요한 데이터를 가공해서 제공
✅ onUsersUpdated 클로저를 통해 데이터가 변경되었을 때 UI를 업데이트하도록 함
✅ 네트워크 로직을 ViewController에서 분리하여 코드가 더 깔끔해짐
✅ 3. View (ViewController)
View는 UI만 담당하고, ViewModel을 통해 데이터를 받아서 UI를 업데이트한다.
class UserViewController: UIViewController {
private let viewModel = UserViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.onUsersUpdated = { [weak self] in
self?.updateUI()
}
viewModel.fetchUsers()
}
func updateUI() {
print("UI 업데이트: \(viewModel.users)")
}
}
📌 핵심 포인트
✅ ViewController는 오직 UI만 담당하며, 데이터 처리는 ViewModel에 위임
✅ ViewModel에서 데이터를 받아 updateUI()를 호출해 화면을 갱신
✅ ViewController는 가볍게 유지되어 유지보수와 테스트가 쉬워짐
5️⃣ MVVM + Combine 활용하기 (데이터 바인딩)
MVVM의 강력한 장점 중 하나는 데이터 바인딩을 활용할 수 있다는 점이다.
Combine을 사용하면 ViewModel에서 변경된 데이터를 자동으로 UI에 반영할 수 있다.
import Combine
class UserViewModel {
private let networkService = NetworkService()
@Published private(set) var users: [User] = []
func fetchUsers() {
networkService.getUsers { [weak self] users in
self?.users = users
}
}
}
class UserViewController: UIViewController {
private let viewModel = UserViewModel()
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.$users
.sink { [weak self] _ in
self?.updateUI()
}
.store(in: &cancellables)
viewModel.fetchUsers()
}
}
✅ 데이터가 변경될 때마다 UI가 자동으로 업데이트됨!
정리
✅ MVVM은 ViewController의 역할을 줄이고, UI와 비즈니스 로직을 분리하는 패턴
✅ Model → 데이터를 저장하고 관리
✅ ViewModel → 데이터를 가공하고 UI 로직을 처리
✅ View(ViewController) → ViewModel에서 제공하는 데이터를 UI에 표시
✅ MVVM을 사용하면 유지보수가 쉬워지고, 유닛 테스트가 용이해짐
✅ RxSwift, Combine을 활용하면 데이터 바인딩을 통해 UI 업데이트를 자동화할 수 있음
이제 MVVM의 개념은 이해했지만, 진짜 중요한 것은 직접 적용해보는 것이다.
이 패턴을 도입하면 프로젝트 구조가 더 깔끔해지고, 유지보수가 쉬워지는 것을 체감할 수 있다.
다음 단계로, 현재 진행 중인 프로젝트에서 ViewController의 역할이 과도한 부분을 찾아보자.
그리고 해당 로직을 ViewModel로 분리해보는 것부터 시작하면 자연스럽게 MVVM 패턴을 익힐 수 있을 것이다.
MVVM을 적용하면 더 유연하고 테스트하기 쉬운 iOS 앱을 만들 수 있다.
기존 프로젝트에서 실험해보고, 변화를 직접 경험해보는게 가장 좋은것 같다!
'프로그래밍 공부' 카테고리의 다른 글
| Clean Architecture - Layer Separation & Dependency Rule (0) | 2025.03.10 |
|---|---|
| iOS 개발에서 의존성 주입(Dependency Injection, DI)이 중요한 이유 (0) | 2025.03.06 |
| ARC(Automatic Reference Counting) – Swift는 어떻게 메모리를 관리할까? (1) | 2025.02.20 |
| Swift – iOS 개발자가 알아야 할 모든 것 (2) | 2025.02.19 |
| 객체 지향 설계의 SOLID 원칙 – 제대로 알고 써보자! (0) | 2025.02.18 |