VIPER: Key Concepts, the View-Interactor-Presenter-Entity-Router Pattern

Author: IT Sectr Published: 2026-02-16 Reading time: 9 min

VIPER (View-Interactor-Presenter-Entity-Router) is a modular architecture developed at Mutual Mobile for iOS applications. VIPER divides the application into five layers: View handles display, Interactor handles business logic, Presenter handles data preparation, Entity handles data models, Router handles navigation between modules. VIPER is the most detailed implementation of the single responsibility principle among mobile architectures. Read more in the article on objc.io.

Key Takeaways

  • VIPER — five components: View, Interactor, Presenter, Entity, Router with clear responsibility boundaries
  • Modularity — each screen (module) is isolated, communication via protocols
  • Router — moves navigation out of Presenter, solving the iOS navigation problem
  • Interactor — contains business logic and does not depend on UIKit, tested with unit tests
  • iOS-native — VIPER was created for UIKit before SwiftUI and remains the standard for large iOS projects

What is VIPER: Five Components of Modular Architecture

VIPER (View-Interactor-Presenter-Entity-Router) is an architectural pattern developed in 2013–2014 at Mutual Mobile for large iOS projects. Each application screen is a separate module of five components with strictly defined responsibilities. VIPER is the strictest implementation of the Single Responsibility Principle in mobile development: no component does what another can do.

View is a passive component responsible only for displaying data passed by the Presenter. View contains no business logic, does not handle navigation, does not make network requests. In iOS — UIViewController with a ViewProtocol. Interactor is the business logic layer that works with Entity and services (network, DB, GPS). Interactor does not import UIKit. Presenter is the mediator between View and Interactor: receives data from Interactor, formats it for display, passes it to View. Presenter also does not import UIKit. Entity — data models (struct, class). Router — manages navigation: creates modules, opens screens, passes data between modules.

ComponentResponsibilityDependencies
ViewDisplay, animations, gesturesUIKit (View only)
InteractorBusiness logic, network, DBEntity, services
PresenterData formatting, View commandsViewProtocol, Interactor
EntityData modelsNone
RouterNavigation, module creationUIViewController (for transitions)

Relationships between components are described by protocols. ViewProtocol defines display methods, InteractorProtocol defines business logic methods, PresenterProtocol defines event handling methods, RouterProtocol defines navigation methods. Each component communicates with another only through a protocol, which allows easy replacement of implementations and isolated testing. On average, a VIPER module for one screen contains 5 protocols + 5 classes + 1 Builder/Assembler = 11 files per screen.

VIPER in Swift: Module, Router and Presenter

Building a VIPER module is done in Builder (or Assembler), which creates all five components and connects them through protocols. Builder is the only place where components know each other's concrete types. After assembly, View is returned outward for display, the rest of the chain is clean and tested in isolation.

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 — business logic
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 — data preparation
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):
                // error handling
            }
        }
    }
}

// Router — navigation
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 — module assembly
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 is a key element of VIPER, implementing Dependency Injection manually. Dependency injection through the constructor (constructor injection) guarantees that a component cannot be created without its dependencies. In modern VIPER, Builder can use Swinject (DI container), but manual assembly remains more transparent for testing. At IT Sectr, we apply VIPER with manual assembly for modules with complex logic — this simplifies code reading for new developers.

Mapper (Formatter) is an optional sixth component of VIPER. Mapper transforms Entity (DB/server models) into ViewModel (display models). Entity contains UserDTO with fields id, first_name, last_name, email. ViewModel — UserDisplayItem with name (first_name + last_name) and email. Mapper is executed in Presenter. If data mapping is complex (multiple Entity → one ViewModel), Mapper is extracted into a separate class for testing.

Communication Between VIPER Modules

VIPER modules are isolated and do not know about each other. Communication between modules happens through Router. When a user clicks the "Profile" button on the user screen, Presenter calls router.navigateToProfile(userId: 42). Router creates a new module via ProfileModuleBuilder.build(userId: 42) and opens it via navigationController.push. Data flow: Module A → Router A → Module B Builder → Module B is created and opened.

Passing data back (for example, selecting a city on the selection screen → returning to the profile editing screen) in VIPER is implemented through delegates or closures. Module B defines the ModuleBDelegate protocol with the didSelectCity(_ city: City) method. Module A implements this protocol. Router A passes the delegate to Module B Builder. When a city is selected, Module B calls delegate?.didSelectCity(city). This is standard iOS practice, familiar to any UIKit developer.

ScenarioMechanismExample
Forward navigationRouter → BuildernavigateToProfile(userId:)
Passing data backDelegatedidSelectCity(_:)
System notificationNotificationCenterUserDidLogout
Event from InteractorPresenter → ViewWebSocket message

NotificationCenter is used for system events (logout, plan change, push notifications) that affect multiple modules simultaneously. Router or AppDelegate subscribes to Notification, creates the required module or updates state. VIPER does not prohibit NotificationCenter — it's important that it is used only for 1-to-many events, while 1-to-1 communication uses delegates or closures.

VIPER vs MVVM and Clean Architecture

VIPER vs MVVM — VIPER requires 2–3 times more code per screen but provides absolute component isolation. MVVM with ViewModel + SwiftUI is simpler and faster but scales worse for teams of 5+ developers. VIPER strictly defines who is responsible for what: Interactor — only business logic, Presenter — formatting, Router — navigation. In MVVM, ViewModel often grows, taking over navigation and business logic.

VIPER vs Clean Architecture — VIPER is a specific case of Clean Architecture adapted for iOS UIKit. Interactor = Use Case, Entity = Domain Model, Presenter = Presentation, Router = Controller in Robert Martin's terms. Clean Architecture adds a Gateway/Repository layer between Interactor and data, which is usually not separated in VIPER. For modern SwiftUI projects, most teams choose Clean Architecture (The Composable Architecture) or MVVM, leaving VIPER for UIKit legacy.

When to choose VIPER — teams of 5+ developers, UIKit projects with 50+ screens, testing requirements above 80%, iOS-only (VIPER is not portable to Android without rewriting). VIPER provides a predictable structure: a new developer understands a module in 15 minutes. However, development speed is 20–30% lower compared to MVVM due to more files. At IT Sectr, we use VIPER for enterprise UIKit projects with teams of 3+ people and prefer Clean Architecture for new SwiftUI projects.

Testing VIPER Modules

VIPER is designed for testing — each component is tested in isolation through protocols. Interactor is tested with mock services: it checks that fetchUser is called with the correct ID and that the result is passed to Presenter. Presenter is tested with mock View and Interactor objects. Router is tested with mock navigation: it checks that navigateToProfile is called with the correct userId and that the correct module is created. View is tested with UI tests (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 objects for VIPER are created manually (class with stored capture properties) or through libraries like Cuckoo / Mockingbird. Manual mock classes are simpler and clearer, especially for training new developers. Each mock stores captured values (capturedUserId, displayedName) and call flags (didShowLoading). At the end of the test, not only is it checked that the method was called, but also with what parameters — this gives confidence in the correctness of the data flow.

Code coverage in IT Sectr VIPER projects reaches 85–95% for Interactor, 90–95% for Presenter, 70–80% for Router, 30–50% for View (via UI tests). View is tested with snapshot tests (SnapshotTesting, 1.5K stars) — this is faster than XCUITest and covers more cases. Overall coverage of a VIPER project is usually 70–80%, which is higher than an MVVM project (50–65%), but requires more time to write tests (30–40% of development time vs 20–25% in MVVM).

Frequently Asked Questions

How many files are in one VIPER module?

At least 11 files: 5 protocols (ViewProtocol, InteractorProtocol, PresenterProtocol, RouterProtocol, Entity), 5 implementations (ViewController, Interactor, Presenter, Router, Entity) and Builder/Assembler. With Mapper (Formatter) — 12–13. For a 50-screen project, that's 550–650 files of just VIPER modules. MVVM requires 3 files per screen (ViewModel, View, Model) — 150 files for 50 screens.

Can VIPER be used on Android?

Yes, theoretically VIPER is portable to Android, but in practice it is not used — Google recommends MVVM with Jetpack. VIPER was created for iOS UIKit, where ViewController is hard to test due to its lifecycle. On Android, Jetpack ViewModel solves the testing problem without VIPER isolation. The Android equivalent of VIPER is Clean Architecture with module/feature breakdown.

What is the difference between VIPER and Clean Architecture?

VIPER is an iOS-specific implementation of Clean Architecture. Interactor corresponds to Use Case, Entity — Domain Model, Presenter — Presentation layer. Clean Architecture adds Repository/Gateway between Interactor and data, which in VIPER are usually implemented inside Interactor. Clean Architecture does not prescribe Router — navigation is left to the implementation.

Is VIPER needed for SwiftUI projects?

No — SwiftUI is designed for MVVM + Combine. VIPER in SwiftUI is redundant: five components per screen with declarative UI is overhead without benefit. For SwiftUI, choose MVVM or TCA (The Composable Architecture). VIPER remains relevant for UIKit legacy and projects where iOS 12 and below is the minimum version.

How to pass data between VIPER modules?

Through Router. Module A calls router.navigateToProfile(userId: id). Router A creates module B through Builder, passes userId. Callback communication — through a delegate: module B defines the ModuleBDelegate protocol, module A implements it and passes it through Router. System events (logout) — through NotificationCenter.

Summary

  • VIPER — five components with strict separation: View, Interactor, Presenter, Entity, Router
  • Modularity — each screen is isolated, Builder assembles dependencies via constructor injection
  • Router — moves navigation out of Presenter, solving the navigation problem on iOS
  • Interactor — clean business logic without UIKit, tested with unit tests
  • Code volume — 11+ files per screen, development is 20–30% slower than MVVM
  • Testing — 70–80% coverage, Interactor and Presenter are tested via mock objects
  • SwiftUI vs UIKit — VIPER for UIKit (legacy), MVVM/TCA for SwiftUI

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also