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) — архітектурний патерн, запропонований Trygve Reenskaug у 1979 році для мови Smalltalk-80. Патерн розділяє застосунок на три шари: Model містить дані та бізнес-логіку, View відповідає за відображення, Controller обробляє введення користувача та оновлює Model і View. Розділення відповідальності дозволяє змінювати кожен шар незалежно — наприклад, замінити View з UIKit на SwiftUI без зміни бізнес-логіки в Model.
Взаємодія компонентів у MVC слідує циклу: користувач взаємодіє з View → Controller отримує подію → Controller оновлює Model → Model повідомляє Controller про зміни → Controller оновлює View. У класичній реалізації Model використовує патерн Observer: при зміні даних Model розсилає сповіщення, Controller підписується та оновлює View. У реалізації Apple Key-Value Observing (KVO) або 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 рекомендує MVC для простих екранів у UIKit-застосунках. Google не рекомендує чистий MVC для Android — офіційна документація пропонує MVVM з Jetpack. Однак знання MVC необхідне для роботи з успадкованими проєктами та для розуміння еволюції архітектурних патернів.
Apple MVC — кастомна реалізація патерну, вбудована в UIKit. UIViewController виконує роль Controller: керує життєвим циклом екрана (viewDidLoad, viewWillAppear, viewDidDisappear), обробляє дотики та дії користувача, оновлює View через IBOutlets. 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, але контролер має прямі посилання на UI-елементи через IBOutlets. Це порушує принцип єдиної відповідальності: контролер відповідає за життєвий цикл, делегатів, datasource, 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 генерує клас з прямими посиланнями на View з XML, усуваючи findViewById. DataBinding додає можливість зв'язувати дані з UI в XML-розмітці через @{user.name}. DataBinding — крок у бік MVVM, оскільки дозволяє передавати дані з Model у View без коду в Activity. Google рекомендує DataBinding для всіх нових проєктів.
Життєвий цикл Android складніший за iOS: Activity може бути знищена та перестворена при повороті екрана, нестачі пам'яті або зміні конфігурації. У чистому MVC контролер (Activity) містить логіку, яка втрачається при знищенні. Це вимагає збереження стану через onSaveInstanceState або ViewModel з Jetpack, що виходить за рамки чистого MVC і наближає архітектуру до MVVM.
Massive View Controller — термін, що описує головну проблему MVC у мобільній розробці. Контролер в iOS та Android бере на себе занадто багато обов'язків: обробка введення, валідація даних, мережева взаємодія, навігація, кешування, анімації, управління життєвим циклом. В результаті контролер розростається до 500–2000 рядків коду, стає важким для читання, тестування та підтримки.
Причини Massive View Controller — архітектура UIKit та Android Framework стимулює розміщення логіки в контролері. Мережеві виклики, обробка JSON, навігація — все це природно писати в Activity або UIViewController, оскільки вони мають доступ до життєвого циклу та UI. Розробник повинен свідомо виносити логіку в окремі класи (Service, Manager, Interactor), що вимагає дисципліни та розуміння архітектурних принципів.
| Проблема MVC | Опис | Рішення |
|---|---|---|
| Сильна зв'язність | Controller знає про View та Model | MVVM — ViewModel не знає про View |
| Складність тестування | Controller залежить від UIKit/Android | Винос логіки в сервіси |
| Життєвий цикл | Стан втрачається при повороті | ViewModel з Jetpack/SwiftUI |
| Відсутність навігації | 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 контролер замінено на ViewModel, який не має посилання на View. Дані передаються через Observable (SwiftUI), LiveData/StateFlow (Android) або Combine/RxSwift. ViewModel тестується юніт-тестами без UI-залежностей. Apple рекомендує MVVM зі SwiftUI з 2019 року, Google — MVVM з LiveData/Flow як офіційну архітектуру Android. MVVM вимагає більше коду для зв'язування, але суттєво покращує тестованість.
MVC vs MVP — у MVP (Model-View-Presenter) Presenter — це тестований шар, який отримує View через інтерфейс. На відміну від MVC, де Controller безпосередньо керує View через UIKit, Presenter не залежить від фреймворку — він працює через абстракцію ViewInterface. MVP популярний в Android-розробці до появи Jetpack і використовується в старих проєктах. Presenter живе довше за Activity і зберігає стан при повороті екрана.
MVC vs Clean Architecture — Clean Architecture додає шари Use Cases (Interactors), Entities, Gateways та Repository. MVC залишається в Presentation шарі, але бізнес-логіка виноситься в Domain шар з Use Cases. Clean Architecture вирішує проблему Massive View Controller радикально — Controller містить лише виклики Use Cases та оновлення View. Недолік — значне збільшення кількості класів та файлів, що виправдано для проєктів від 50+ екранів.
// MVC в iOS: 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 роль контролера виконує ViewModel, який не має посилання на View — дані передаються через механізми прив'язки. MVVM краще тестується, оскільки ViewModel не залежить від UIKit або Android Framework. Apple рекомендує MVVM зі SwiftUI, Google — MVVM з Jetpack Compose.
Так, MVC залишається робочим патерном для простих екранів та прототипів. Apple рекомендує MVC для UIKit-застосунків з простими екранами. Для складних проєктів з безліччю екранів, мережевими запитами та кешуванням краще обрати MVVM, VIPER або Clean Architecture. Початківцям рекомендується освоїти MVC перед вивченням складніших патернів.
Model тестується ізольовано — це звичайні об'єкти даних та бізнес-логіка. Controller складно тестувати через залежність від UIKit або Android Framework. Рекомендується виносити бізнес-логіку з контролера в окремі сервіси або interactor'и, які тестуються юніт-тестами. View зазвичай не тестується юніт-тестами — для неї використовуються UI-тести та скріншотні тести.
На iOS — MVVM зі SwiftUI та Combine, стандарт Apple з 2019 року. На Android — MVVM з LiveData або StateFlow, офіційно рекомендований Google. Для великих проєктів з командами від 5 розробників — Clean Architecture з VIPER на iOS або Clean Architecture на Android з розділенням на модулі за фічами. Для легасі-проєктів з MVC — поступовий рефакторинг з виносом логіки в окремі сервіси.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також