이제 MVVM 단계로 넘어가서, MVVM → VIPER로의 변화를 차근히 살펴보겠습니다.
이번에는 “왜 MVVM에서 만족하지 못하고 VIPER로 갔는가?” 즉, MVVM의 한계 → VIPER의 필요성을 먼저 알아보겠습니다.
🧩 2️⃣ MVVM → VIPER : “확장성과 협업의 한계”
🧠 MVVM의 본질
MVVM은 분명히 MVC보다 훨씬 나은 구조입니다.
ViewController는 화면만 관리하고, ViewModel이 데이터 로직을 담당하니까요.
하지만, 프로젝트가 커지고 팀 단위로 개발하기 시작하면
곧 두 가지 큰 문제가 드러납니다.
⚠️ 문제 1️⃣ — “ViewModel이 커진다 (Massive ViewModel)”
MVVM은 Controller의 부담을 ViewModel로 옮겼을 뿐,
ViewModel이 또다시 비대해질 수 있습니다.
예를 들어, 쇼핑 앱의 상품 상세 화면을 생각해봅시다.
상품 상세 화면 (ProductDetailViewModel)
class ProductDetailViewModel {
// 1. UI용 데이터
@Published var title: String = ""
@Published var price: String = ""
// 2. 비즈니스 로직
func fetchProduct() { ... }
func fetchReviews() { ... }
// 3. 화면 전환 로직
func showCartView() { ... }
func showReviewList() { ... }
// 4. 유효성 검사, 애널리틱스, 로깅, 알림 등
}
ViewModel이 결국 다시 모든 걸 담당하게 됩니다.
MVVM은 구조상 “어디까지가 ViewModel의 역할인가?”가 모호합니다.
- 화면 이동 (Routing)을 해야 하나?
- 네트워크 로직을 직접 호출해도 되나?
- 로깅이나 유효성 검사는 어디서 하지?
이 질문들에 답하기 어렵다는 게
MVVM의 가장 큰 약점입니다.
⚠️ 문제 2️⃣ — “화면 전환과 모듈 의존성 문제”
MVVM은 각 화면(View + ViewModel) 단위로는 깔끔하지만,
화면 간 이동이나 모듈 간 연결을 처리하기가 어렵습니다.
예를 들어,
상품 화면 → 결제 화면으로 이동해야 할 때
viewModel.showPaymentView()
이런 코드가 들어가면
결제 화면에 대한 의존성이 ViewModel 안으로 들어갑니다.
즉, “ViewModel은 UI 로직과 관련이 없어야 한다”는 MVVM의 철학이 깨지죠.
⚙️ 예시 코드로 보는 한계
class LoginViewModel {
@Published var username = ""
@Published var password = ""
func login() {
AuthService.login(user: username, pass: password) { [weak self] result in
switch result {
case .success:
// ❌ ViewModel이 화면 전환 로직을 가짐
self?.navigateToMainView()
case .failure(let error):
// ❌ 에러 핸들링도 ViewModel 안에서
self?.showError(error.localizedDescription)
}
}
}
}
여기서 문제가 되는 부분:
- ViewModel이 네비게이션 담당 (
navigateToMainView) - ViewModel이 에러 표시 담당 (
showError) - ViewModel이 비즈니스 로직까지 수행 (
AuthService직접 호출)
결국 “ViewModel이 Presenter처럼 행동”하게 됩니다.
즉, ViewModel이 비즈니스 로직 + 네비게이션 + 뷰 상태를 전부 책임집니다.
💡 해결의 방향 — “각 역할을 명확히 분리하자”
그래서 나온 개념이 VIPER입니다.
VIPER는 역할을 다섯 개로 쪼갭니다.
| 구성 요소 | 역할 |
|---|---|
| V (View) | UI 표시, 사용자 입력 감지 |
| I (Interactor) | 비즈니스 로직, 데이터 처리 |
| P (Presenter) | View와 Interactor 사이 중재자 |
| E (Entity) | 순수 데이터 모델 |
| R (Router) | 화면 전환 (Navigation) 담당 |
📦 VIPER 구조 핵심
단방향 의존성을 강제합니다.
View → Presenter → Interactor → Entity
↑ ↓
└────────────── Router즉, Presenter는 View와 Interactor를 알고 있지만,
서로의 내부 구현에는 접근할 수 없습니다.
🧩 실제 예시 (Counter App)
구조
CounterEntity.swift
CounterInteractor.swift
CounterPresenter.swift
CounterRouter.swift
CounterViewController.swift1️⃣ Entity
struct CounterEntity {
var count = 0
}
2️⃣ Interactor
protocol CounterInteractorInput {
func increase()
}
protocol CounterInteractorOutput: AnyObject {
func didUpdateCount(_ count: Int)
}
class CounterInteractor: CounterInteractorInput {
weak var output: CounterInteractorOutput?
private var entity = CounterEntity()
func increase() {
entity.count += 1
output?.didUpdateCount(entity.count)
}
}
3️⃣ Presenter
protocol CounterViewInput: AnyObject {
func updateCount(_ text: String)
}
class CounterPresenter: CounterInteractorOutput {
weak var view: CounterViewInput?
var interactor: CounterInteractorInput?
func didTapIncrease() {
interactor?.increase()
}
func didUpdateCount(_ count: Int) {
view?.updateCount("Count: \(count)")
}
}
4️⃣ Router
class CounterRouter {
static func createModule() -> UIViewController {
let view = CounterViewController()
let presenter = CounterPresenter()
let interactor = CounterInteractor()
view.presenter = presenter
presenter.view = view
presenter.interactor = interactor
interactor.output = presenter
return view
}
}
✅ MVVM → VIPER로의 진화 요약
| 항목 | MVVM | VIPER |
|---|---|---|
| 역할 분리 | View + ViewModel | View + Interactor + Presenter + Router |
| 화면 이동 | ViewModel이 직접 수행 | Router가 전담 |
| 테스트 | ViewModel 중심 | 각 레이어 단독 테스트 가능 |
| 의존성 | View ↔ ViewModel | 단방향 (View→Presenter→Interactor) |
| 대규모 확장성 | 어려움 | 매우 높음 |
| 유지보수 | 단일 모듈 단위 | 독립된 모듈 단위 |
🧠 핵심 요약
MVVM은 “로직 분리”를 가능하게 했지만,
VIPER는 “모듈화와 팀 협업”을 가능하게 했다.
MVVM이 한 명이 관리하는 앱에 좋다면,
VIPER는 여러 팀이 협업하는 대규모 앱에 적합합니다.
보면 알겠지만 하나의 화면을 만드는데 굉장히 많은 파일들이 생성되게 됩니다. 개인적인 취향이지만 아키텍쳐를 구현하면서 어디까지 작성할 것인지는 생각해보아야 합니다.
회사에서 일하는 것을 기준으로 공부를 하기때문에 모든 것을 다 알아야하지만, 개인적인 작은 앱을 만들때는 MVC로 만드는 것이 더 나을 수도 있습니다.
'iOS > Swift' 카테고리의 다른 글
| RIBs (0) | 2025.12.11 |
|---|---|
| MVVM (Model-View-ViewModel) (0) | 2025.12.05 |
| MVC (Model - View - Controller) (0) | 2025.12.04 |
| iOS에서 UI 변경이 바로 적용되지 않는 이유 (0) | 2025.05.09 |
| ViewModel의 위치? View vs ViewController (0) | 2025.05.07 |