VIPER: кључни појмови, образац View-Interactor-Presenter-Entity-Router

Аутор: IT Sectr Објављено: 2026-02-16 Време читања: 9 мин

VIPER (View-Interactor-Presenter-Entity-Router) — модуларна архитектура развијена у компанији Mutual Mobile за iOS апликације. VIPER дели апликацију на пет слојева: View је задужен за приказ, Interactor — за пословну логику, Presenter — за припрему података, Entity — за моделе података, Router — за навигацију између модула. VIPER је најдетаљнија имплементација принципа јединствене одговорности међу мобилним архитектурама. Више — у чланку на objc.io.

Главно

  • VIPER — пет компоненти: View, Interactor, Presenter, Entity, Router са јасним границама одговорности
  • Модуларност — сваки екран (модул) је изолован, комуникација преко протокола
  • Router — износи навигацију из Presenter-а, решавајући проблем навигације iOS-а
  • Interactor — садржи пословну логику и не зависи од UIKit-а, тестира се unit-тестовима
  • iOS-native — VIPER је креиран за UIKit пре појаве SwiftUI и остаје стандард за велике iOS пројекте

Шта је VIPER: пет компоненти модуларне архитектуре

VIPER (View-Interactor-Presenter-Entity-Router) — архитектурални образац развијен 2013–2014. године у компанији Mutual Mobile за велике iOS пројекте. Сваки екран апликације је засебан модул од пет компоненти са строго дефинисаним одговорностима. VIPER је најстрожа имплементација принципа јединствене одговорности (Single Responsibility Principle) у мобилном развоју: ниједна компонента не ради оно што може да уради друга.

View — пасивна компонента задужена само за приказ података које јој проследи Presenter. View не садржи пословну логику, не обрађује навигацију, не позива мрежне захтеве. У iOS-у — UIViewController са протоколом ViewProtocol. Interactor — слој пословне логике који ради са Entity-ем и сервисима (мрежа, база података, GPS). Interactor не импортује UIKit. Presenter — посредник између View-а и Interactor-а: прима податке од Interactor-а, форматира их за приказ, прослеђује View-у. Presenter такође не импортује UIKit. Entity — модели података (struct, class). Router — управља навигацијом: креира модуле, отвара екране, прослеђује податке између модула.

КомпонентаОдговорностЗависности
ViewПриказ, анимације, гестовиUIKit (само View)
InteractorПословна логика, мрежа, база податакаEntity, сервиси
PresenterФорматирање података, команде View-уViewProtocol, Interactor
EntityМодели податакаНема
RouterНавигација, креирање модулаUIViewController (за прелазе)

Везе између компоненти су описане протоколима. ViewProtocol дефинише методе приказа, InteractorProtocol — методе пословне логике, PresenterProtocol — методе обраде догађаја, RouterProtocol — методе навигације. Свака компонента комуницира са другом само преко протокола, што омогућава лаку замену имплементација и изоловано тестирање. У просеку VIPER модул од једног екрана садржи 5 протокола + 5 класа + 1 Builder/Assembler = 11 датотека по екрану.

VIPER на Swift-у: модул, Router и Presenter

Изградња VIPER модула се обавља у Builder-у (или Assembler-у), који креира свих пет компоненти и повезује их преко протокола. Builder је једино место где компоненте знају конкретне типове једна за другу. Након изградње, View се враћа напоље за приказ, остатак ланца је чист и тестира се изоловано.

swift
// Протокол — View
protocol UserViewProtocol: AnyObject {
    func display(name: String)
    func display(email: String)
    func showLoading()
    func hideLoading()
}

// Протокол — Interactor
protocol UserInteractorProtocol {
    func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void)
}

// Протокол — Router
protocol UserRouterProtocol {
    func navigateToProfile(userId: Int)
}

// Interactor — пословна логика
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 — припрема података
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):
                // обрада грешке
            }
        }
    }
}

// Router — навигација
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 — изградња модула
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 — кључни елемент VIPER-а који имплементира ручно убризгавање зависности (Dependency Injection). Убризгавање зависности кроз конструктор (constructor injection) гарантује да компонента не може бити креирана без својих зависности. У савременом VIPER-у Builder може користити Swinject (DI контејнер), али ручна изградња остаје транспарентнија за тестирање. У IT Sectr-у примењујемо VIPER са ручном изградњом за модуле са сложеном логиком — то поједностављује читање кода новим програмерима.

Mapper (Formatter) — опциона шеста компонента VIPER-а. Mapper претвара Entity (моделе базе/сервера) у ViewModel (моделе приказа). Entity садржи UserDTO са пољима id, first_name, last_name, email. ViewModel — UserDisplayItem са name (first_name + last_name) и email. Mapper се извршава у Presenter-у. Ако је мапирање података сложено (више Entity → један ViewModel), Mapper се издваја у засебну класу ради тестирања.

Комуникација између VIPER модула

VIPER модули су изоловани и не знају један за другог. Комуникација између модула се одвија преко Router-а. Када корисник кликне на дугме „Профил" на екрану корисника, Presenter позива router.navigateToProfile(userId: 42). Router креира нови модул преко ProfileModuleBuilder.build(userId: 42) и отвара га преко navigationController.push. Ток података: Модул A → Router A → Модул B Builder → Модул B се креира и отвара.

Прослеђивање података назад (нпр. изабран је град на екрану избора → враћен на екран за уређивање профила) у VIPER-у се реализује кроз делегате или затварања (closure). Модул B дефинише протокол ModuleBDelegate са методом didSelectCity(_ city: City). Модул A имплементира овај протокол. Router A прослеђује делегата у Module B Builder. При избору града, Модул B позива delegate?.didSelectCity(city). Ово је стандардна iOS пракса, позната сваком UIKit програмеру.

СценариоМеханизамПример
Прелаз напредRouter → BuildernavigateToProfile(userId:)
Прослеђивање података назадDelegatedidSelectCity(_:)
Системско обавештењеNotificationCenterUserDidLogout
Догађај из Interactor-аPresenter → ViewWebSocket message

NotificationCenter се користи за системске догађаје (одјава, промена тарифе, push обавештења) који истовремено погађају више модула. Router или AppDelegate се претплаћује на Notification, креира потребни модул или ажурира стање. VIPER не забрањује NotificationCenter — важно је да се користи само за догађаје 1-према-више, а за комуникацију 1-на-1 примењују делегати или затварања.

Поређење VIPER-а са MVVM и Clean Architecture

VIPER vs MVVM — VIPER захтева 2–3 пута више кода по екрану, али пружа апсолутну изолацију компоненти. MVVM са ViewModel + SwiftUI је једноставнији и бржи, али се лошије скалира на тимове од 5+ програмера. VIPER строго одређује ко је за шта задужен: Interactor — само пословна логика, Presenter — форматирање, Router — навигација. У MVVM-у ViewModel често нарасте, преузимајући навигацију и пословну логику.

VIPER vs Clean Architecture — VIPER је посебан случај Clean Architecture-е, прилагођен за iOS UIKit. Interactor = Use Case, Entity = Domain Model, Presenter = Presentation, Router = Controller у терминима Роберта Мартина. Clean Architecture додаје Gateway/Repository слој између Interactor-а и података, који се у VIPER-у обично не издваја. За савремене пројекте на SwiftUI-ју већина тимова бира Clean Architecture (The Composable Architecture) или MVVM, остављајући VIPER за заоставштину на UIKit-у.

Када изабрати VIPER — тимови од 5 програмера, пројекат на UIKit-у од 50 екрана, захтеви за тестирањем изнад 80%, само iOS (VIPER се не преноси на Android без преписивања). VIPER пружа предвидљиву структуру: нови програмер разуме модул за 15 минута. Међутим, брзина развоја је 20–30% нижа у поређењу са MVVM због већег броја датотека. У IT Sectr-у користимо VIPER за enterprise пројекте на UIKit-у са тимовима од 3 особе и дајемо предност Clean Architecture-и за нове пројекте на SwiftUI-ју.

Тестирање VIPER модула

VIPER је дизајниран за тестирање — свака компонента се тестира изоловано преко протокола. Interactor се тестира са mock-сервисима: проверава се да ли је fetchUser позван са тачним ID-јем и да ли је резултат прослеђен Presenter-у. Presenter се тестира са mock-објектима View и Interactor-а. Router се тестира са mock-навигацијом: проверава се да ли је navigateToProfile позван са тачним userId-јем и да ли је креиран исправан модул. View се тестира UI тестовима (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-објекти за VIPER се креирају ручно (класа са ускладиштеним capture-својствима) или преко библиотека Cuckoo / Mockingbird. Ручне mock-класе су једноставније и разумљивије, посебно за обучавање нових програмера. Сваки mock чува снимљене вредности (capturedUserId, displayedName) и заставице позива (didShowLoading). На крају теста проверава се не само да ли је метод позван, већ и са којим параметрима — то даје сигурност у исправност тока података.

Покривеност кода у VIPER пројектима IT Sectr-а достиже 85–95% за Interactor, 90–95% за Presenter, 70–80% за Router, 30–50% за View (кроз UI тестове). View се тестира screenshot тестовима (SnapshotTesting, 1,5K звезда) — то је брже од XCUITest-а и покрива више случајева. Укупна покривеност VIPER пројекта је обично 70–80%, што је више од MVVM пројекта (50–65%), али захтева више времена за писање тестова (30–40% времена развоја наспрам 20–25% у MVVM-у).

Често постављана питања

Колико датотека има један VIPER модул?

Минимално 11 датотека: 5 протокола (ViewProtocol, InteractorProtocol, PresenterProtocol, RouterProtocol, Entity), 5 имплементација (ViewController, Interactor, Presenter, Router, Entity) и Builder/Assembler. Са Mapper-ом (Formatter) — 12–13. За пројекат од 50 екрана то је 550–650 датотека само VIPER модула. MVVM захтева 3 датотеке по екрану (ViewModel, View, Model) — 150 датотека за 50 екрана.

Може ли се VIPER користити на Android-у?

Да, теоретски VIPER је преносив на Android, али се у пракси не примењује — Google препоручује MVVM са Jetpack-ом. VIPER је креиран за iOS UIKit, где је ViewController тешко тестирати због животног циклуса. На Android-у Jetpack ViewModel решава проблем тестирања без VIPER изолације. Android аналог VIPER-а — Clean Architecture са поделом на module/feature.

Која је разлика између VIPER-а и Clean Architecture-е?

VIPER је имплементација Clean Architecture-е специфична за iOS. Interactor одговара Use Case-у, Entity — Domain Model-у, Presenter — Presentation слоју. Clean Architecture додаје Repository/Gateway између Interactor-а и података, који су у VIPER-у обично имплементирани унутар Interactor-а. Clean Architecture не прописује Router — навигација остаје на дискрецију имплементације.

Да ли је VIPER потребан за SwiftUI пројекте?

Не — SwiftUI је дизајниран за MVVM + Combine. VIPER у SwiftUI-ју је претерано обиман: пет компоненти за један екран са декларативним UI-јем је overhead без користи. За SwiftUI бирајте MVVM или TCA (The Composable Architecture). VIPER остаје актуелан за UIKit заоставштину и пројекте где су iOS 12 и ниже минимална верзија.

Како проследити податке између VIPER модула?

Преко Router-а. Модул A позива router.navigateToProfile(userId: id). Router A креира модул B преко Builder-а, прослеђује userId. Повратно прослеђивање — преко делегата: модул B дефинише протокол ModuleBDelegate, модул A га имплементира и прослеђује преко Router-а. Системски догађаји (одјава) — преко NotificationCenter-а.

Закључци

  • VIPER — пет компоненти са строгом поделом: View, Interactor, Presenter, Entity, Router
  • Модуларност — сваки екран је изолован, Builder саставља зависности кроз constructor injection
  • Router — износи навигацију из Presenter-а, решавајући проблем навигације на iOS-у
  • Interactor — чиста пословна логика без UIKit-а, тестира се unit-тестовима
  • Количина кода — 11+ датотека по екрану, развој 20–30% спорији од MVVM-а
  • Тестирање — 70–80% покривеност, Interactor и Presenter се тестирају кроз mock-објекте
  • SwiftUI vs UIKit — VIPER за UIKit (заоставштина), MVVM/TCA за SwiftUI

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође