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
// Protocol — View
protocol UserViewProtocol: AnyObject {
    func display(name: String)
    func display(email: String)
    func showLoading()
    func hideLoading()
}

// Protocol — Interactor
protocol UserInteractorProtocol {
    func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void)
}

// Protocol — 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 message

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 создаются вручную (класс с хранимыми свойствами-capture) или через библиотеки 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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