VIPER (View-Interactor-Presenter-Entity-Router) — modulární architektura vyvinutá ve společnosti Mutual Mobile pro iOS aplikace. VIPER rozděluje aplikaci do pěti vrstev: View zodpovídá za zobrazení, Interactor — za obchodní logiku, Presenter — za přípravu dat, Entity — za datové modely, Router — za navigaci mezi moduly. VIPER je nejpodrobnější implementací principu jedné odpovědnosti mezi mobilními architekturami. Více — v článku na objc.io.
Hlavní body
VIPER (View-Interactor-Presenter-Entity-Router) — architektonický vzor vyvinutý v letech 2013–2014 ve společnosti Mutual Mobile pro velké iOS projekty. Každá obrazovka aplikace je samostatný modul z pěti komponent s přesně definovanými odpovědnostmi. VIPER je nejpřísnější implementací principu jedné odpovědnosti (Single Responsibility Principle) v mobilním vývoji: žádná komponenta nedělá to, co může udělat jiná.
View — pasivní komponenta zodpovědná pouze za zobrazení dat předaných Presenterem. View neobsahuje obchodní logiku, nezpracovává navigaci, neprovádí síťové požadavky. V iOS — UIViewController s protokolem ViewProtocol. Interactor — vrstva obchodní logiky pracující s Entity a službami (síť, databáze, GPS). Interactor neimportuje UIKit. Presenter — prostředník mezi View a Interactorem: přijímá data od Interactoru, formátuje je pro zobrazení, předává je View. Presenter také neimportuje UIKit. Entity — datové modely (struct, class). Router — řídí navigaci: vytváří moduly, otevírá obrazovky, předává data mezi moduly.
| Komponenta | Odpovědnost | Závislosti |
|---|---|---|
| View | Zobrazení, animace, gesta | UIKit (pouze View) |
| Interactor | Obchodní logika, síť, databáze | Entity, služby |
| Presenter | Formátování dat, příkazy View | ViewProtocol, Interactor |
| Entity | Datové modely | Žádné |
| Router | Navigace, vytváření modulů | UIViewController (pro přechody) |
Spojení mezi komponentami jsou popsána protokoly. ViewProtocol definuje metody zobrazení, InteractorProtocol — metody obchodní logiky, PresenterProtocol — metody zpracování událostí, RouterProtocol — metody navigace. Každá komponenta komunikuje s jinou pouze přes protokol, což umožňuje snadnou výměnu implementací a izolované testování. Průměrný VIPER modul z jedné obrazovky obsahuje 5 protokolů + 5 tříd + 1 Builder/Assembler = 11 souborů na obrazovku.
Stavba VIPER modulu se provádí v Builderu (nebo Assembleru), který vytvoří všech pět komponent a propojí je přes protokoly. Builder je jediné místo, kde komponenty znají své konkrétní typy. Po sestavení je View vráceno ven k zobrazení, zbytek řetězce je čistý a testuje se izolovaně.
// Protokol — View
protocol UserViewProtocol: AnyObject {
func display(name: String)
func display(email: String)
func showLoading()
func hideLoading()
}
// Protokol — Interactor
protocol UserInteractorProtocol {
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void)
}
// Protokol — Router
protocol UserRouterProtocol {
func navigateToProfile(userId: Int)
}
// Interactor — obchodní logika
final class UserInteractor: UserInteractorProtocol {
private let service: UserService
init(service: UserService) { self.service = service }
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
service.fetchUser(id: id, completion: completion)
}
}
// Presenter — příprava dat
final class UserPresenter {
private weak var view: UserViewProtocol?
private let interactor: UserInteractorProtocol
private let router: UserRouterProtocol
init(interactor: UserInteractorProtocol, router: UserRouterProtocol) {
self.interactor = interactor
self.router = router
}
func setView(_ view: UserViewProtocol) {
self.view = view
}
func viewDidLoad() {
view?.showLoading()
interactor.fetchUser(id: 42) { [weak self] result in
guard let self else { return }
self.view?.hideLoading()
switch result {
case .success(let user):
self.view?.display(name: user.name)
self.view?.display(email: user.email)
case .failure(let error):
// zpracování chyby
}
}
}
}
// Router — navigace
final class UserRouter: UserRouterProtocol {
private weak var viewController: UIViewController?
func setViewController(_ vc: UIViewController) {
viewController = vc
}
func navigateToProfile(userId: Int) {
let profileModule = ProfileModuleBuilder.build(userId: userId)
viewController?.navigationController?.pushViewController(profileModule, animated: true)
}
}
// Builder — sestavení modulu
enum UserModuleBuilder {
static func build(userId: Int) -> UIViewController {
let service = UserService()
let interactor = UserInteractor(service: service)
let router = UserRouter()
let presenter = UserPresenter(interactor: interactor, router: router)
let viewController = UserViewController(presenter: presenter)
presenter.setView(viewController)
router.setViewController(viewController)
return viewController
}
}
Builder/Assembler — klíčový prvek VIPER, který implementuje ruční vkládání závislostí (Dependency Injection). Vkládání závislostí přes konstruktor (constructor injection) zaručuje, že komponenta nemůže být vytvořena bez svých závislostí. V moderním VIPER může Builder používat Swinject (DI kontejner), ale ruční sestavení zůstává transparentnější pro testování. V IT Sectr používáme VIPER s ručním sestavením pro moduly se složitou logikou — to zjednodušuje čtení kódu novými vývojáři.
Mapper (Formatter) — volitelná šestá komponenta VIPER. Mapper převádí Entity (modely databáze/serveru) na ViewModel (modely zobrazení). Entity obsahuje UserDTO s poli id, first_name, last_name, email. ViewModel — UserDisplayItem s name (first_name + last_name) a email. Mapper se provádí v Presenteru. Pokud je mapování dat složité (více Entity → jeden ViewModel), Mapper je vyčleněn do samostatné třídy pro testování.
Moduly VIPER jsou izolované a neví o sobě navzájem. Komunikace mezi moduly probíhá přes Router. Když uživatel klikne na tlačítko „Profil" na obrazovce uživatele, Presenter zavolá router.navigateToProfile(userId: 42). Router vytvoří nový modul přes ProfileModuleBuilder.build(userId: 42) a otevře ho přes navigationController.push. Tok dat: Modul A → Router A → Modul B Builder → Modul B je vytvořen a otevřen.
Předávání dat zpět (například bylo vybráno město na výběrové obrazovce → vráceno na obrazovku úpravy profilu) se v VIPER implementuje přes delegáty nebo closure. Modul B definuje protokol ModuleBDelegate s metodou didSelectCity(_ city: City). Modul A implementuje tento protokol. Router A předá delegáta do Module B Builder. Při výběru města Modul B zavolá delegate?.didSelectCity(city). To je standardní praxe v iOS, známá každému UIKit vývojáři.
| Scénář | Mechanismus | Příklad |
|---|---|---|
| Přechod vpřed | Router → Builder | navigateToProfile(userId:) |
| Předání dat zpět | Delegate | didSelectCity(_:) |
| Systémové oznámení | NotificationCenter | UserDidLogout |
| Událost z Interactoru | Presenter → View | WebSocket message |
NotificationCenter se používá pro systémové události (odhlášení, změna tarifu, push oznámení), které ovlivňují několik modulů současně. Router nebo AppDelegate se přihlásí k odběru Notification, vytvoří potřebný modul nebo aktualizuje stav. VIPER nezakazuje NotificationCenter — důležité je, aby se používal pouze pro události 1-k-mnoha, a pro komunikaci 1-na-1 se používaly delegáty nebo closure.
VIPER vs MVVM — VIPER vyžaduje 2–3krát více kódu na obrazovku, ale poskytuje absolutní izolaci komponent. MVVM s ViewModel + SwiftUI je jednodušší a rychlejší, ale hůře se škáluje v týmech 5+ vývojářů. VIPER striktně určuje, kdo za co zodpovídá: Interactor — pouze obchodní logika, Presenter — formátování, Router — navigace. V MVVM ViewModel často naroste a přebírá navigaci i obchodní logiku.
VIPER vs Clean Architecture — VIPER je specifický případ Clean Architecture, přizpůsobený pro iOS UIKit. Interactor = Use Case, Entity = Domain Model, Presenter = Presentation, Router = Controller v terminologii Roberta Martina. Clean Architecture přidává vrstvu Gateway/Repository mezi Interactor a data, která se v VIPER obvykle neodděluje. Pro moderní projekty ve SwiftUI většina týmů volí Clean Architecture (The Composable Architecture) nebo MVVM, a VIPER nechává pro starší kód na UIKit.
Kdy zvolit VIPER — týmy od 5 vývojářů, projekt na UIKit od 50 obrazovek, požadavky na testování nad 80 %, pouze iOS (VIPER se bez přepsání nepřenáší na Android). VIPER poskytuje předvídatelnou strukturu: nový vývojář pochopí modul do 15 minut. Rychlost vývoje je však o 20–30 % nižší ve srovnání s MVVM kvůli většímu počtu souborů. V IT Sectr používáme VIPER pro enterprise projekty na UIKit s týmy od 3 lidí a dáváme přednost Clean Architecture pro nové projekty ve SwiftUI.
VIPER je navržen pro testování — každá komponenta se testuje izolovaně přes protokoly. Interactor se testuje s mock službami: kontroluje se, zda byl fetchUser zavolán se správným ID a zda byl výsledek předán Presenteru. Presenter se testuje s mock objekty View a Interactoru. Router se testuje s mock navigací: kontroluje se, zda byl navigateToProfile zavolán se správným userId a zda byl vytvořen správný modul. View se testuje UI testy (XCUITest).
import XCTest
final class UserPresenterTests: XCTestCase {
func testViewDidLoad_callsFetchUserAndUpdatesView() {
// Given
let view = MockUserView()
let interactor = MockUserInteractor()
let router = MockUserRouter()
let presenter = UserPresenter(interactor: interactor, router: router)
presenter.setView(view)
let expectedUser = User(id: 42, name: "John", email: "john@test.com")
interactor.result = .success(expectedUser)
// When
presenter.viewDidLoad()
// Then
XCTAssertEqual(interactor.capturedUserId, 42)
XCTAssertEqual(view.displayedName, "John")
XCTAssertTrue(view.didShowLoading)
}
}
Mock objekty pro VIPER se vytvářejí ručně (třída s uloženými capture vlastnostmi) nebo pomocí knihoven Cuckoo / Mockingbird. Ruční mock třídy jsou jednodušší a srozumitelnější, zejména pro školení nových vývojářů. Každý mock ukládá zachycené hodnoty (capturedUserId, displayedName) a příznaky volání (didShowLoading). Na konci testu se kontroluje nejen to, zda byla metoda zavolána, ale také s jakými parametry — to dává jistotu ve správnost toku dat.
Pokrytí kódu v projektech VIPER v IT Sectr dosahuje 85–95 % pro Interactor, 90–95 % pro Presenter, 70–80 % pro Router, 30–50 % pro View (přes UI testy). View se testuje snímkovými testy (SnapshotTesting, 1,5K hvězdiček) — je to rychlejší než XCUITest a pokrývá více případů. Celkové pokrytí VIPER projektu je obvykle 70–80 %, což je vyšší než u MVVM projektu (50–65 %), ale vyžaduje více času na psaní testů (30–40 % času vývoje oproti 20–25 % v MVVM).
Často kladené otázky
Minimálně 11 souborů: 5 protokolů (ViewProtocol, InteractorProtocol, PresenterProtocol, RouterProtocol, Entity), 5 implementací (ViewController, Interactor, Presenter, Router, Entity) a Builder/Assembler. S Mapperem (Formatter) — 12–13. Pro projekt s 50 obrazovkami to je 550–650 souborů jen pro VIPER moduly. MVVM vyžaduje 3 soubory na obrazovku (ViewModel, View, Model) — 150 souborů pro 50 obrazovek.
Ano, teoreticky je VIPER přenositelný na Android, ale v praxi se nepoužívá — Google doporučuje MVVM s Jetpack. VIPER byl vytvořen pro iOS UIKit, kde je ViewController obtížně testovatelný kvůli životnímu cyklu. Na Androidu Jetpack ViewModel řeší problém testování bez izolace VIPER. Androidový ekvivalent VIPER — Clean Architecture s rozdělením na module/feature.
VIPER je implementace Clean Architecture specifická pro iOS. Interactor odpovídá Use Case, Entity — Domain Model, Presenter — vrstvě Presentation. Clean Architecture přidává Repository/Gateway mezi Interactor a data, což je v VIPER obvykle implementováno uvnitř Interactoru. Clean Architecture nepředepisuje Router — navigace zůstává na rozhodnutí implementace.
Ne — SwiftUI je navržen pro MVVM + Combine. VIPER ve SwiftUI je nadbytečný: pět komponent na jednu obrazovku s deklarativním UI je režie bez přínosu. Pro SwiftUI zvolte MVVM nebo TCA (The Composable Architecture). VIPER zůstává relevantní pro starší kód UIKit a projekty, kde je iOS 12 a nižší minimální verzí.
Přes Router. Modul A zavolá router.navigateToProfile(userId: id). Router A vytvoří modul B přes Builder, předá userId. Zpětné předání — přes delegáta: modul B definuje protokol ModuleBDelegate, modul A ho implementuje a předá přes Router. Systémové události (odhlášení) — přes NotificationCenter.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také