MVC (Model-View-Controller) — wzorzec architektoniczny, dzielący aplikację na trzy komponenty: Model odpowiada za dane i logikę biznesową, View — za interfejs użytkownika, Controller — za przetwarzanie wejścia i koordynację Modelu i View. W iOS MVC jest implementowany przez UIViewController, w Android — przez Activity i Fragment. MVC pozostaje podstawowym wzorcem, na którym zbudowano MVVM, MVP i Clean Architecture. Więcej — na MVC in Cocoa Core.
Najważniejsze
MVC (Model-View-Controller) — wzorzec architektoniczny zaproponowany przez Trygve Reenskaug w 1979 roku dla języka Smalltalk-80. Wzorzec dzieli aplikację na trzy warstwy: Model zawiera dane i logikę biznesową, View odpowiada za wyświetlanie, Controller przetwarza dane wejściowe użytkownika i aktualizuje Model i View. Podział odpowiedzialności pozwala zmieniać każdą warstwę niezależnie — na przykład zastąpić View z UIKit na SwiftUI bez zmiany logiki biznesowej w Modelu.
Interakcja komponentów w MVC przebiega w cyklu: użytkownik wchodzi w interakcję z View → Controller otrzymuje zdarzenie → Controller aktualizuje Model → Model powiadamia Controller o zmianach → Controller aktualizuje View. W klasycznej implementacji Model używa wzorca Observer: przy zmianie danych Model rozsyła powiadomienia, Controller subskrybuje i aktualizuje View. W implementacji Apple Key-Value Observing (KVO) lub NotificationCenter pełnią tę rolę.
| Komponent | Odpowiedzialność | Przykład w iOS | Przykład w Android |
|---|---|---|---|
| Model | Dane, logika biznesowa, sieć | Struct User, CoreData | Data class, Repository |
| View | Wyświetlanie UI | Storyboard, XIB, UIView | Układ XML, Jetpack Compose |
| Controller | Przetwarzanie wejścia, koordynacja | UIViewController | Activity, Fragment |
MVC w nowoczesnym tworzeniu aplikacji mobilnych jest używany rzadziej niż 10 lat temu, ale pozostaje obowiązkowy do zrozumienia. Apple zaleca MVC dla prostych ekranów w aplikacjach UIKit. Google nie zaleca czystego MVC dla Android — oficjalna dokumentacja sugeruje MVVM z Jetpack. Jednak znajomość MVC jest niezbędna do pracy z projektami legacy i do zrozumienia ewolucji wzorców architektonicznych.
Apple MVC — niestandardowa implementacja wzorca wbudowana w UIKit. UIViewController pełni rolę Controller: zarządza cyklem życia ekranu (viewDidLoad, viewWillAppear, viewDidDisappear), przetwarza dotknięcia i akcje użytkownika, aktualizuje View przez IBOutlets. View jest tworzone w Interface Builder (storyboard lub XIB) lub programowo. Model — to dowolne obiekty danych: serwisy sieciowe, stosy CoreData, struktury Swift.
final class UserViewController: UIViewController {
// View (przez outlet storyboard)
@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 aktualizuje View
self?.nameLabel.text = user.name
self?.emailLabel.text = user.email
}
}
}
Problem Apple MVC — View i Controller są ściśle powiązane. UIViewController jednocześnie zarządza i View, i logiką. Storyboard przechowuje View w XML, ale kontroler ma bezpośrednie referencje do elementów UI przez IBOutlets. Narusza to zasadę pojedynczej odpowiedzialności: kontroler odpowiada za cykl życia, delegaty, datasource, target-action i animacje. W rezultacie standardowy ekran aplikacji iOS zawiera 200–500 linii w kontrolerze.
Cykl życia ViewController — Apple udostępnia 6 metod cyklu życia: loadView (ręczne tworzenie View), viewDidLoad (po załadowaniu View do pamięci), viewWillAppear (przed pojawieniem się na ekranie), viewDidAppear (po animacji), viewWillDisappear (przed opuszczeniem ekranu), viewDidDisappear (po opuszczeniu). Każda metoda — miejsce do umieszczenia logiki w MVC. Używanie tych metod do logiki biznesowej przyspiesza rozrost kontrolera.
Android MVC — Activity i Fragment pełnią rolę Controller, pliki XML layout — View, dowolna klasa POJO z danymi — Model. Activity zarządza cyklem życia ekranu: onCreate, onStart, onResume, onPause, onStop, onDestroy. Fragment — podekran wewnątrz Activity z własnym cyklem życia. View (XML) jest oddzielone od Controller i ładowane przez setContentView lub LayoutInflater. Model — repozytoria, bazy danych, wywołania sieciowe.
class UserActivity : AppCompatActivity() {
// View przez układ XML
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 i DataBinding — nowoczesne narzędzia zmniejszające powiązanie Controller i View. ViewBinding generuje klasę z bezpośrednimi referencjami do View z XML, eliminując findViewById. DataBinding dodaje możliwość wiązania danych z UI w znacznikach XML przez @{user.name}. DataBinding — krok w stronę MVVM, ponieważ pozwala przesyłać dane z Model do View bez kodu w Activity. Google zaleca DataBinding dla wszystkich nowych projektów.
Cykl życia Android jest bardziej złożony niż iOS: Activity może zostać zniszczona i odtworzona przy obrocie ekranu, braku pamięci lub zmianie konfiguracji. W czystym MVC kontroler (Activity) zawiera logikę, która ginie przy zniszczeniu. Wymaga to zapisywania stanu przez onSaveInstanceState lub ViewModel z Jetpack, co wykracza poza ramy czystego MVC i przybliża architekturę do MVVM.
Massive View Controller — termin opisujący główny problem MVC w tworzeniu aplikacji mobilnych. Kontroler w iOS i Android bierze na siebie zbyt wiele obowiązków: przetwarzanie wejścia, walidacja danych, komunikacja sieciowa, nawigacja, buforowanie, animacje, zarządzanie cyklem życia. W rezultacie kontroler rozrasta się do 500–2000 linii kodu, staje się trudny do czytania, testowania i utrzymania.
Przyczyny Massive View Controller — architektura UIKit i Android Framework stymuluje umieszczanie logiki w kontrolerze. Wywołania sieciowe, przetwarzanie JSON, nawigacja — wszystko to naturalnie pisze się w Activity lub UIViewController, ponieważ mają one dostęp do cyklu życia i UI. Deweloper musi świadomie wynosić logikę do osobnych klas (Service, Manager, Interactor), co wymaga dyscypliny i zrozumienia zasad architektonicznych.
| Problem MVC | Opis | Rozwiązanie |
|---|---|---|
| Silne powiązanie | Controller zna View i Model | MVVM — ViewModel nie zna View |
| Trudność testowania | Controller zależy od UIKit/Android | Wyniesienie logiki do serwisów |
| Cykl życia | Stan ginie przy obrocie | ViewModel z Jetpack/SwiftUI |
| Brak nawigacji | Controller zarządza przejściami | Wzorzec Coordinator, Router |
Testowanie MVC — Model testuje się izolowanie testami jednostkowymi. Controller trudno testować z powodu zależności od UIKit/UIFoundation. XCTest nie pozwala tworzyć UIViewController bez okna widoku. Dla Android ActivityTestRule i Robolectric częściowo rozwiązują problem, ale testy są wolne. View zwykle nie jest testowane testami jednostkowymi — do UI stosuje się testy zrzutów ekranu i UI (XCUITest, Espresso).
Kiedy MVC jest uzasadniony — proste ekrany z jednego-dwóch elementów (ekran logowania, profilu, ustawień). Prototypy i MVP do weryfikacji hipotez — MVC pisze się szybciej bez dodatkowych warstw. Projekty z małą bazą kodu do 10–15 ekranów. W złożonych projektach MVC prowadzi do narastania długu technicznego i wymaga refaktoryzacji co 6–12 miesięcy.
MVC vs MVVM — główna różnica: w MVVM kontroler jest zastąpiony ViewModel, który nie ma referencji do View. Dane są przesyłane przez Observable (SwiftUI), LiveData/StateFlow (Android) lub Combine/RxSwift. ViewModel testuje się testami jednostkowymi bez zależności UI. Apple zaleca MVVM ze SwiftUI od 2019 roku, Google — MVVM z LiveData/Flow jako oficjalną architekturę Android. MVVM wymaga więcej kodu do wiązania, ale znacznie poprawia testowalność.
MVC vs MVP — w MVP (Model-View-Presenter) Presenter to testowalna warstwa, która otrzymuje View przez interfejs. W przeciwieństwie do MVC, gdzie Controller bezpośrednio zarządza View przez UIKit, Presenter nie zależy od frameworka — działa przez abstrakcję ViewInterface. MVP jest popularny w Android do pojawienia się Jetpack i jest używany w starych projektach. Presenter żyje dłużej niż Activity i zachowuje stan przy obrocie ekranu.
MVC vs Clean Architecture — Clean Architecture dodaje warstwy Use Cases (Interactors), Entities, Gateways i Repository. MVC pozostaje w warstwie Presentation, ale logika biznesowa jest wyniesiona do warstwy Domain z Use Cases. Clean Architecture rozwiązuje problem Massive View Controller radykalnie — Controller zawiera tylko wywołania Use Cases i aktualizację View. Wadą jest znaczne zwiększenie liczby klas i plików, co jest uzasadnione dla projektów od 50+ ekranów.
// MVC w iOS: Controller zawiera wszystko
class OrderViewController: UIViewController {
func placeOrder() {
// Walidacja + sieć + aktualizacja UI
guard Validation.isValid(total) else { return }
NetworkService.shared.submit(order) { [weak self] result in
self?.handleResult(result)
}
}
}
// MVVM: logika w ViewModel
class OrderViewModel: ObservableObject {
@Published var state: OrderState = .idle
func placeOrder() { /* logika biznesowa */ }
}
Wybór architektury zależy od wielkości zespołu, projektu i wymaganej testowalności. Dla zespołu 1–2 deweloperów i projektu do 20 ekranów wystarczy MVVM. Dla dużego zespołu od 5 deweloperów i projektu od 50 ekranów — Clean Architecture z modułową strukturą. MVC pozostaje aktualny do zrozumienia ewolucji architektur, do wsparcia projektów legacy i dla prostych ekranów w UIKit bez złożonej logiki biznesowej.
Często zadawane pytania
Głównym problemem jest Massive View Controller. W iOS kontroler UIViewController odpowiada za wszystko: przetwarzanie wejścia, aktualizację View, pracę z siecią, nawigację i cykl życia. W Android Activity/Fragment wykonuje analogiczne funkcje. W rezultacie kontroler rozrasta się do tysięcy linii kodu, staje się trudny do testowania i utrzymania, naruszając zasadę pojedynczej odpowiedzialności.
W MVC kontroler bezpośrednio aktualizuje View i przetwarza dane wejściowe użytkownika. W MVVM rolę kontrolera pełni ViewModel, który nie ma referencji do View — dane są przesyłane przez mechanizmy wiązania. MVVM lepiej się testuje, ponieważ ViewModel nie zależy od UIKit ani Android Framework. Apple zaleca MVVM ze SwiftUI, Google — MVVM z Jetpack Compose.
Tak, MVC pozostaje działającym wzorcem dla prostych ekranów i prototypów. Apple zaleca MVC dla aplikacji UIKit z prostymi ekranami. Dla złożonych projektów z wieloma ekranami, zapytaniami sieciowymi i buforowaniem lepiej wybrać MVVM, VIPER lub Clean Architecture. Początkującym deweloperom zaleca się opanowanie MVC przed nauką bardziej złożonych wzorców.
Model testuje się izolowanie — to zwykłe obiekty danych i logika biznesowa. Controller trudno testować z powodu zależności od UIKit lub Android Framework. Zaleca się wynoszenie logiki biznesowej z kontrolera do osobnych serwisów lub interaktorów, które testuje się testami jednostkowymi. View zwykle nie jest testowane testami jednostkowymi — do niego używa się testów UI i testów zrzutów ekranu.
Na iOS — MVVM ze SwiftUI i Combine, standard Apple od 2019 roku. Na Android — MVVM z LiveData lub StateFlow, oficjalnie zalecany przez Google. Dla dużych projektów z zespołami od 5 deweloperów — Clean Architecture z VIPER na iOS lub Clean Architecture na Android z podziałem na moduły według funkcji. Dla projektów legacy z MVC — stopniowa refaktoryzacja z wynoszeniem logiki do osobnych serwisów.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również