VIPER: klíčové pojmy, vzor View-Interactor-Presenter-Entity-Router

Autor: IT Sectr Publikováno: 2026-02-16 Doba čtení: 9 min

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 — pět komponent: View, Interactor, Presenter, Entity, Router s jasnými hranicemi odpovědnosti
  • Modularita — každá obrazovka (modul) je izolovaná, komunikace přes protokoly
  • Router — přenáší navigaci z Presenteru, řeší problém navigace v iOS
  • Interactor — obsahuje obchodní logiku a není závislý na UIKit, testuje se unit testy
  • iOS-native — VIPER byl vytvořen pro UIKit před příchodem SwiftUI a zůstává standardem pro velké iOS projekty

Co je VIPER: pět komponent modulární architektury

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.

KomponentaOdpovědnostZávislosti
ViewZobrazení, animace, gestaUIKit (pouze View)
InteractorObchodní logika, síť, databázeEntity, služby
PresenterFormátování dat, příkazy ViewViewProtocol, Interactor
EntityDatové modelyŽádné
RouterNavigace, 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.

VIPER ve Swift: modul, Router a Presenter

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ě.

swift
// 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í.

Komunikace mezi moduly VIPER

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ářMechanismusPříklad
Přechod vpředRouter → BuildernavigateToProfile(userId:)
Předání dat zpětDelegatedidSelectCity(_:)
Systémové oznámeníNotificationCenterUserDidLogout
Událost z InteractoruPresenter → ViewWebSocket 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.

Srovnání VIPER s MVVM a Clean Architecture

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.

Testování VIPER modulů

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).

swift
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

Kolik souborů má jeden VIPER modul?

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.

Lze použít VIPER na Androidu?

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.

Jaký je rozdíl mezi VIPER a Clean Architecture?

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.

Je VIPER potřeba pro SwiftUI projekty?

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í.

Jak předat data mezi VIPER moduly?

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í

  • VIPER — pět komponent s přísným oddělením: View, Interactor, Presenter, Entity, Router
  • Modularita — každá obrazovka je izolovaná, Builder sestavuje závislosti přes constructor injection
  • Router — přenáší navigaci z Presenteru, řeší problém navigace v iOS
  • Interactor — čistá obchodní logika bez UIKit, testovaná unit testy
  • Množství kódu — 11+ souborů na obrazovku, vývoj o 20–30 % pomalejší než MVVM
  • Testování — 70–80% pokrytí, Interactor a Presenter testovány přes mock objekty
  • SwiftUI vs UIKit — VIPER pro UIKit (starší kód), MVVM/TCA pro SwiftUI

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í.

Prodiskutovat projekt

Přečtěte si také