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Форматування даних, команди ViewViewProtocol, 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. Data flow: Module A → Router A → Module B Builder → Module B створюється і відкривається.

Передача даних назад (наприклад, вибрали місто на екрані вибору → повернули на екран редагування профілю) в VIPER реалізується через делегати або замикання. Модуль B визначає протокол ModuleBDelegate з методом didSelectCity(_ city: City). Модуль A реалізує цей протокол. Router A передає делегат в Module B Builder. При виборі міста Module B викликає delegate?.didSelectCity(city). Це стандартна iOS-практика, знайома будь-якому розробнику UIKit.

СценарійМеханізмПриклад
Перехід впередRouter → BuildernavigateToProfile(userId:)
Передача даних назадDelegatedidSelectCity(_:)
Сповіщення системиNotificationCenterUserDidLogout
Подія з InteractorPresenter → ViewWebSocket повідомлення

NotificationCenter використовується для системних подій (розлогінювання, зміна тарифу, пуш-сповіщення), які зачіпають кілька модулів одночасно. Router або AppDelegate підписується на Notification, створює потрібний модуль або оновлює стан. VIPER не забороняє NotificationCenter — важливо, щоб він використовувався тільки для подій 1-to-many, а для 1-to-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-only (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 створюються вручну (клас зі збереженими властивостями-захопленнями) або через бібліотеки Cuckoo / Mockingbird. Ручні mock-класи простіші та зрозуміліші, особливо для навчання нових розробників. Кожен mock зберігає captured-значення (capturedUserId, displayedName) та прапорці виклику (didShowLoading). Наприкінці тесту перевіряється не лише те, що метод викликаний, але й з якими параметрами — це дає впевненість у коректності потоку даних.

Покриття коду в VIPER-проектах IT Sectr досягає 85–95% для Interactor, 90–95% для Presenter, 70–80% для Router, 30–50% для View (через UI-тести). View тестується скріншотними тестами (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 — iOS-специфічна реалізація Clean Architecture. 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 — це оверхед без вигоди. Для 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також