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. Поток на данни: Модул 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
Събитие от InteractorPresenter → 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 се тества с тестове на екранни снимки (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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също