MVC(Model-View-Controller)는 애플리케이션을 세 가지 구성 요소로 나누는 아키텍처 패턴입니다. Model은 데이터와 비즈니스 로직을 담당하고, View는 사용자 인터페이스를 담당하며, Controller는 입력 처리와 Model 및 View 조정을 담당합니다. iOS에서 MVC는 UIViewController를 통해 구현되고, Android에서는 Activity와 Fragment를 통해 구현됩니다. MVC는 MVVM, MVP 및 Clean Architecture가 구축되는 기본 패턴으로 남아 있습니다. 자세한 내용은 MVC in Cocoa Core를 참조하세요.
주요 포인트
MVC(Model-View-Controller)는 1979년 Trygve Reenskaug이 Smalltalk-80 언어를 위해 제안한 아키텍처 패턴입니다. 이 패턴은 애플리케이션을 세 개의 계층으로 나눕니다: Model은 데이터와 비즈니스 로직을 포함하고, View는 표시를 담당하며, Controller는 사용자 입력을 처리하고 Model과 View를 업데이트합니다. 책임 분리를 통해 각 계층을 독립적으로 변경할 수 있습니다 — 예를 들어, Model의 비즈니스 로직을 변경하지 않고 View를 UIKit에서 SwiftUI로 교체할 수 있습니다.
구성 요소 상호 작용은 MVC에서 다음 사이클을 따릅니다: 사용자가 View와 상호 작용 → Controller가 이벤트 수신 → Controller가 Model 업데이트 → Model이 Controller에 변경 알림 → Controller가 View 업데이트. 클래식 구현에서 Model은 Observer 패턴을 사용합니다: 데이터가 변경되면 Model이 알림을 보내고, Controller가 구독하여 View를 업데이트합니다. Apple 구현에서는 KVO(Key-Value Observing) 또는 NotificationCenter가 이 역할을 수행합니다.
| 구성 요소 | 책임 | iOS 예시 | Android 예시 |
|---|---|---|---|
| Model | 데이터, 비즈니스 로직, 네트워크 | Struct User, CoreData | Data class, Repository |
| View | UI 표시 | Storyboard, XIB, UIView | XML layout, Jetpack Compose |
| Controller | 입력 처리, 조정 | UIViewController | Activity, Fragment |
현대 모바일 개발에서 MVC는 10년 전보다 덜 사용되지만, 이해하는 것은 여전히 필수적입니다. Apple은 UIKit 애플리케이션의 간단한 화면에 MVC를 권장합니다. Google은 Android에 순수 MVC를 권장하지 않습니다 — 공식 문서는 Jetpack과 함께 MVVM을 제안합니다. 그러나 레거시 프로젝트 작업과 아키텍처 패턴의 진화를 이해하기 위해서는 MVC 지식이 필요합니다.
Apple MVC는 UIKit에 내장된 패턴의 사용자 지정 구현입니다. UIViewController는 Controller 역할을 합니다: 화면 라이프사이클(viewDidLoad, viewWillAppear, viewDidDisappear)을 관리하고, 터치 및 사용자 동작을 처리하며, IBOutlets를 통해 View를 업데이트합니다. View는 Interface Builder(Storyboard 또는 XIB)에서 또는 프로그래밍 방식으로 생성됩니다. Model — 네트워크 서비스, CoreData 스택, Swift 구조체 등 모든 데이터 객체입니다.
final class UserViewController: UIViewController {
// View(storyboard outlet을 통해)
@IBOutlet private var nameLabel: UILabel!
@IBOutlet private var emailLabel: UILabel!
// Model
private let userService = UserService()
override func viewDidLoad() {
super.viewDidLoad()
loadUser()
}
private func loadUser() {
userService.fetchUser { [weak self] user in
// Controller가 View를 업데이트
self?.nameLabel.text = user.name
self?.emailLabel.text = user.email
}
}
}
Apple MVC의 문제 — View와 Controller가 강하게 결합되어 있습니다. UIViewController는 View와 로직을 동시에 관리합니다. Storyboard는 View를 XML에 저장하지만, 컨트롤러는 IBOutlets를 통해 UI 요소에 직접 참조를 가집니다. 이는 단일 책임 원칙을 위반합니다: 컨트롤러는 라이프사이클, 델리게이트, 데이터소스, target-action 및 애니메이션을 담당합니다. 결과적으로 표준 iOS 앱 화면에는 컨트롤러에 200~500줄이 포함됩니다.
ViewController 라이프사이클 — Apple은 6개의 라이프사이클 메서드를 제공합니다: loadView(수동 View 생성), viewDidLoad(메모리에 View 로드 후), viewWillAppear(화면에 나타나기 전), viewDidAppear(애니메이션 후), viewWillDisappear(화면을 떠나기 전), viewDidDisappear(떠난 후). 각 메서드는 MVC에서 로직을 배치하는 위치입니다. 이러한 메서드를 비즈니스 로직에 사용하면 컨트롤러 비대화가 가속화됩니다.
Android MVC — Activity와 Fragment가 Controller 역할을 하고, XML layout 파일이 View 역할을 하며, 데이터가 있는 POJO 클래스가 Model 역할을 합니다. Activity는 화면 라이프사이클을 관리합니다: onCreate, onStart, onResume, onPause, onStop, onDestroy. Fragment는 자체 라이프사이클을 가진 Activity 내의 하위 화면입니다. View(XML)는 Controller와 분리되어 setContentView 또는 LayoutInflater를 통해 로드됩니다. Model — 리포지토리, 데이터베이스, 네트워크 호출.
class UserActivity : AppCompatActivity() {
// View(XML layout을 통해)
private lateinit var binding: ActivityUserBinding
// Model
private val userRepository = UserRepository()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUserBinding.inflate(layoutInflater)
setContentView(binding.root)
loadUser()
}
private fun loadUser() {
userRepository.getUser { user ->
runOnUiThread {
binding.nameText.text = user.name
binding.emailText.text = user.email
}
}
}
}
Android ViewBinding 및 DataBinding — Controller와 View 간의 결합을 줄이는 최신 도구입니다. ViewBinding은 XML에서 Views에 대한 직접 참조가 있는 클래스를 생성하여 findViewById를 없앱니다. DataBinding은 @{user.name}을 통해 XML 마크업에서 데이터를 UI에 바인딩하는 기능을 추가합니다. DataBinding은 MVVM으로 가는 한 걸음입니다. Activity에서 코드 없이 Model에서 View로 데이터를 전달할 수 있기 때문입니다. Google은 모든 새 프로젝트에 DataBinding을 권장합니다.
Android 라이프사이클은 iOS보다 복잡합니다: 화면 회전, 메모리 부족 또는 구성 변경 시 Activity가 파괴되고 재생성될 수 있습니다. 순수 MVC에서 컨트롤러(Activity)는 파괴 시 손실되는 로직을 포함합니다. 이는 onSaveInstanceState 또는 Jetpack의 ViewModel을 통해 상태를 저장해야 하며, 이는 순수 MVC를 넘어 아키텍처를 MVVM에 가깝게 만듭니다.
Massive View Controller는 모바일 개발에서 MVC의 주요 문제를 설명하는 용어입니다. iOS 및 Android의 컨트롤러는 너무 많은 책임을 집니다: 입력 처리, 데이터 검증, 네트워크 상호 작용, 탐색, 캐싱, 애니메이션, 라이프사이클 관리. 결과적으로 컨트롤러는 500~2000줄의 코드로 비대해져 읽기, 테스트 및 유지 관리가 어려워집니다.
Massive View Controller의 원인 — UIKit 및 Android Framework의 아키텍처는 컨트롤러에 로직을 배치하도록 장려합니다. 네트워크 호출, JSON 처리, 탐색 — 이 모든 것은 라이프사이클과 UI에 접근할 수 있기 때문에 자연스럽게 Activity 또는 UIViewController에 위치하게 됩니다. 개발자는 의식적으로 로직을 별도의 클래스(Service, Manager, Interactor)로 추출해야 하며, 이는 규율과 아키텍처 원칙에 대한 이해가 필요합니다.
| MVC 문제 | 설명 | 해결책 |
|---|---|---|
| 강한 결합 | Controller가 View와 Model을 알고 있음 | MVVM — ViewModel이 View를 모름 |
| 테스트 복잡성 | Controller가 UIKit/Android에 의존 | 서비스로 로직 추출 |
| 라이프사이클 | 회전 시 상태 손실 | Jetpack/SwiftUI의 ViewModel |
| 탐색 부족 | Controller가 전환 관리 | Coordinator 패턴, Router |
MVC 테스트 — Model은 단위 테스트로 격리되어 테스트됩니다. Controller는 UIKit/UIFoundation에 의존하기 때문에 테스트하기 어렵습니다. XCTest는 뷰 윈도우 없이 UIViewController를 생성할 수 없습니다. Android의 경우 ActivityTestRule과 Robolectric이 부분적으로 문제를 해결하지만 테스트가 느립니다. View는 일반적으로 단위 테스트로 테스트되지 않습니다 — UI에는 스크린샷 테스트와 UI 테스트(XCUITest, Espresso)가 사용됩니다.
MVC가 정당화되는 경우 — 하나 또는 두 개의 요소가 있는 간단한 화면(로그인 화면, 프로필, 설정). 가설 검증을 위한 프로토타입 및 MVP — MVC는 추가 계층 없이 더 빠르게 작성할 수 있습니다. 최대 10~15개 화면의 작은 코드베이스 프로젝트. 복잡한 프로젝트에서 MVC는 기술 부채 축적으로 이어지며 6~12개월마다 리팩토링이 필요합니다.
MVC vs MVVM — 주요 차이점: MVVM에서 컨트롤러는 View에 대한 참조가 없는 ViewModel로 대체됩니다. 데이터는 Observable(SwiftUI), LiveData/StateFlow(Android) 또는 Combine/RxSwift를 통해 전달됩니다. ViewModel은 UI 종속성 없이 단위 테스트로 테스트할 수 있습니다. Apple은 2019년부터 SwiftUI와 함께 MVVM을 권장하고, Google은 LiveData/Flow와 함께 MVVM을 공식 Android 아키텍처로 권장합니다. MVVM은 바인딩에 더 많은 코드가 필요하지만 테스트 용이성을 크게 향상시킵니다.
MVC vs MVP — MVP(Model-View-Presenter)에서 Presenter는 인터페이스를 통해 View를 수신하는 테스트 가능한 계층입니다. Controller가 UIKit을 통해 직접 View를 관리하는 MVC와 달리, Presenter는 프레임워크에 의존하지 않으며 ViewInterface 추상화를 통해 작동합니다. MVP는 Jetpack 이전 Android 개발에서 인기가 있었고 레거시 프로젝트에서 사용됩니다. Presenter는 Activity보다 오래 지속되며 화면 회전 시 상태를 유지합니다.
MVC vs Clean Architecture — Clean Architecture는 계층을 추가합니다: Use Cases(Interactors), Entities, Gateways 및 Repository. MVC는 Presentation 계층에 남지만 비즈니스 로직은 Use Cases와 함께 Domain 계층으로 이동합니다. Clean Architecture는 Massive View Controller 문제를 근본적으로 해결합니다 — Controller는 Use Cases 호출과 View 업데이트만 포함합니다. 단점은 클래스와 파일 수가 크게 증가한다는 것이며, 이는 50개 이상의 화면이 있는 프로젝트에서 정당화됩니다.
// iOS의 MVC: Controller가 모든 것을 포함
class OrderViewController: UIViewController {
func placeOrder() {
// 검증 + 네트워크 + UI 업데이트
guard Validation.isValid(total) else { return }
NetworkService.shared.submit(order) { [weak self] result in
self?.handleResult(result)
}
}
}
// MVVM: ViewModel에 로직
class OrderViewModel: ObservableObject {
@Published var state: OrderState = .idle
func placeOrder() { /* 비즈니스 로직 */ }
}
아키텍처 선택은 팀 규모, 프로젝트 범위 및 필요한 테스트 용이성에 따라 다릅니다. 1~2명의 개발자 팀과 최대 20개 화면의 프로젝트에는 MVVM이 적합합니다. 5명 이상의 개발자 팀과 50개 이상의 화면이 있는 프로젝트에는 모듈식 구조의 Clean Architecture가 적합합니다. MVC는 여전히 중요합니다 — 아키텍처의 진화를 이해하고, 레거시 프로젝트를 유지 관리하며, 복잡한 비즈니스 로직이 없는 간단한 UIKit 화면을 위해서입니다.
자주 묻는 질문
주요 문제는 Massive View Controller입니다. iOS에서 UIViewController는 모든 것을 처리합니다: 입력 처리, View 업데이트, 네트워킹, 탐색 및 라이프사이클. Android에서 Activity/Fragment는 유사한 기능을 수행합니다. 결과적으로 컨트롤러는 수천 줄의 코드로 비대해져 테스트와 유지 관리가 어려워지고 단일 책임 원칙을 위반합니다.
MVC에서 컨트롤러는 직접 View를 업데이트하고 사용자 입력을 처리합니다. MVVM에서 컨트롤러 역할은 View에 대한 참조가 없는 ViewModel이 수행합니다 — 데이터는 바인딩 메커니즘을 통해 전달됩니다. ViewModel이 UIKit이나 Android Framework에 의존하지 않기 때문에 MVVM은 테스트하기 더 쉽습니다. Apple은 SwiftUI와 함께 MVVM을 권장하고, Google은 Jetpack Compose와 함께 MVVM을 권장합니다.
네, MVC는 간단한 화면과 프로토타입에 여전히 작동하는 패턴입니다. Apple은 간단한 화면의 UIKit 애플리케이션에 MVC를 권장합니다. 많은 화면, 네트워크 요청 및 캐싱이 있는 복잡한 프로젝트의 경우 MVVM, VIPER 또는 Clean Architecture를 선택하는 것이 좋습니다. 초보 개발자는 더 복잡한 패턴을 배우기 전에 MVC를 마스터하는 것이 좋습니다.
Model은 격리되어 테스트됩니다 — 일반적인 데이터 객체와 비즈니스 로직입니다. Controller는 UIKit 또는 Android Framework에 의존하기 때문에 테스트하기 어렵습니다. 컨트롤러에서 비즈니스 로직을 별도의 서비스나 인터랙터로 추출하여 단위 테스트로 테스트하는 것이 좋습니다. View는 일반적으로 단위 테스트로 테스트되지 않습니다 — UI 테스트와 스크린샷 테스트가 사용됩니다.
iOS에서는 — SwiftUI 및 Combine과 함께 MVVM, 2019년부터 Apple의 표준입니다. Android에서는 — LiveData 또는 StateFlow와 함께 MVVM, Google이 공식적으로 권장합니다. 5명 이상의 개발자 팀이 있는 대규모 프로젝트의 경우 — iOS에서는 VIPER와 함께 Clean Architecture, Android에서는 기능 기반 모듈 분리와 함께 Clean Architecture. MVC 레거시 프로젝트의 경우 — 별도의 서비스로 로직을 추출하는 점진적 리팩토링.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.