VIPER: nyckelbegrepp, mönstret View-Interactor-Presenter-Entity-Router

Författare: IT Sectr Publicerad: 2026-02-16 Lästid: 9 min

VIPER (View-Interactor-Presenter-Entity-Router) — modulär arkitektur utvecklad på Mutual Mobile för iOS-applikationer. VIPER delar upp applikationen i fem lager: View ansvarar för visning, Interactor — för affärslogik, Presenter — för databeräkning, Entity — för datamodeller, Router — för navigering mellan moduler. VIPER är den mest detaljerade implementeringen av principen om ensamt ansvar bland mobila arkitekturer. Mer — i artikeln på objc.io.

Huvudpunkter

  • VIPER — fem komponenter: View, Interactor, Presenter, Entity, Router med tydliga ansvarsgränser
  • Modularitet — varje skärm (modul) är isolerad, kommunikation via protokoll
  • Router — flyttar navigeringen från Presenter, löser iOS-navigeringsproblemet
  • Interactor — innehåller affärslogik och är inte beroende av UIKit, testas med enhetstester
  • iOS-native — VIPER skapades för UIKit innan SwiftUI kom och förblir standarden för stora iOS-projekt

Vad är VIPER: fem komponenter i modulär arkitektur

VIPER (View-Interactor-Presenter-Entity-Router) — ett arkitekturmönster utvecklat 2013–2014 på Mutual Mobile för stora iOS-projekt. Varje skärm i applikationen är en separat modul av fem komponenter med strikt definierade ansvarsområden. VIPER är den strängaste implementeringen av principen om ensamt ansvar (Single Responsibility Principle) inom mobil utveckling: ingen komponent gör vad en annan kan göra.

View — passiv komponent som endast ansvarar för att visa data som skickats av Presenter. View innehåller ingen affärslogik, hanterar inte navigering, gör inte nätverksförfrågningar. I iOS — UIViewController med protokollet ViewProtocol. Interactor — affärslogiklagret som arbetar med Entity och tjänster (nätverk, databas, GPS). Interactor importerar inte UIKit. Presenter — förmedlare mellan View och Interactor: tar emot data från Interactor, formaterar för visning, skickar till View. Presenter importerar inte heller UIKit. Entity — datamodeller (struct, class). Router — hanterar navigering: skapar moduler, öppnar skärmar, skickar data mellan moduler.

KomponentAnsvarBeroenden
ViewVisning, animationer, gesterUIKit (endast View)
InteractorAffärslogik, nätverk, databasEntity, tjänster
PresenterDataformatering, View-kommandonViewProtocol, Interactor
EntityDatamodellerInga
RouterNavigering, skapa modulerUIViewController (för övergångar)

Förbindelser mellan komponenter beskrivs av protokoll. ViewProtocol definierar visningsmetoder, InteractorProtocol — metoder för affärslogik, PresenterProtocol — metoder för händelsehantering, RouterProtocol — navigeringsmetoder. Varje komponent kommunicerar med en annan endast via protokollet, vilket möjliggör enkel utbyte av implementeringar och isolerad testning. En genomsnittlig VIPER-modul från en skärm innehåller 5 protokoll + 5 klasser + 1 Builder/Assembler = 11 filer per skärm.

VIPER i Swift: modul, Router och Presenter

Byggandet av VIPER-modulen utförs i Builder (eller Assembler), som skapar alla fem komponenter och kopplar ihop dem via protokoll. Builder är den enda platsen där komponenterna känner till varandras konkreta typer. Efter byggandet returneras View utåt för visning, resten av kedjan är ren och testas isolerat.

swift
// Protokoll — View
protocol UserViewProtocol: AnyObject {
    func display(name: String)
    func display(email: String)
    func showLoading()
    func hideLoading()
}

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

// Protokoll — Router
protocol UserRouterProtocol {
    func navigateToProfile(userId: Int)
}

// Interactor — affärslogik
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 — databeräkning
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):
                // felhantering
            }
        }
    }
}

// Router — navigering
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 — modulbyggnad
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 — nyckelelementet i VIPER som implementerar manuell beroendeinjektion (Dependency Injection). Beroendeinjektion via konstruktor (constructor injection) garanterar att en komponent inte kan skapas utan sina beroenden. I modern VIPER kan Builder använda Swinject (DI-container), men manuellt bygge förblir mer transparent för testning. På IT Sectr tillämpar vi VIPER med manuellt bygge för moduler med komplex logik — detta förenklar kodläsningen för nya utvecklare.

Mapper (Formatter) — en valfri sjätte komponent i VIPER. Mapper omvandlar Entity (databas/servermodeller) till ViewModel (visningsmodeller). Entity innehåller UserDTO med fälten id, first_name, last_name, email. ViewModel — UserDisplayItem med name (first_name + last_name) och email. Mapper exekveras i Presenter. Om datamappning är komplex (flera Entity → en ViewModel), flyttas Mapper till en separat klass för testning.

Kommunikation mellan VIPER-moduler

VIPER-moduler är isolerade och vet inte om varandra. Kommunikation mellan moduler sker via Router. När användaren trycker på knappen „Profil" på användarskärmen anropar Presenter router.navigateToProfile(userId: 42). Router skapar en ny modul via ProfileModuleBuilder.build(userId: 42) och öppnar den via navigationController.push. Dataflöde: Modul A → Router A → Modul B Builder → Modul B skapas och öppnas.

Dataöverföring tillbaka (till exempel valdes en stad på väljarskärmen → returnerades till profilredigeringsskärmen) i VIPER implementeras via delegater eller closures. Modul B definierar protokollet ModuleBDelegate med metoden didSelectCity(_ city: City). Modul A implementerar detta protokoll. Router A skickar delegaten till Module B Builder. Vid val av stad anropar Modul B delegate?.didSelectCity(city). Detta är en standardpraxis inom iOS, bekant för varje UIKit-utvecklare.

ScenarioMekanismExempel
Övergång framåtRouter → BuildernavigateToProfile(userId:)
Dataöverföring tillbakaDelegatedidSelectCity(_:)
SystemmeddelandeNotificationCenterUserDidLogout
Händelse från InteractorPresenter → ViewWebSocket message

NotificationCenter används för systemhändelser (utloggning, tariffändring, push-meddelanden) som påverkar flera moduler samtidigt. Router eller AppDelegate prenumererar på Notification, skapar den nödvändiga modulen eller uppdaterar status. VIPER förbjuder inte NotificationCenter — viktigt är att det endast används för 1-till-många-händelser, och för 1-till-1-kommunikation används delegater eller closures.

Jämförelse av VIPER med MVVM och Clean Architecture

VIPER vs MVVM — VIPER kräver 2–3 gånger mer kod per skärm, men ger absolut isolering av komponenter. MVVM med ViewModel + SwiftUI är enklare och snabbare, men skalar sämre i team med 5+ utvecklare. VIPER bestämmer strikt vem som ansvarar för vad: Interactor — endast affärslogik, Presenter — formatering, Router — navigering. I MVVM växer ViewModel ofta och tar över navigering och affärslogik.

VIPER vs Clean Architecture — VIPER är ett specifikt fall av Clean Architecture, anpassat för iOS UIKit. Interactor = Use Case, Entity = Domain Model, Presenter = Presentation, Router = Controller i Robert Martins terminologi. Clean Architecture lägger till ett Gateway/Repository-lager mellan Interactor och data, som i VIPER vanligtvis inte separeras. För moderna projekt i SwiftUI väljer de flesta team Clean Architecture (The Composable Architecture) eller MVVM, och lämnar VIPER för äldre kod på UIKit.

När ska man välja VIPER — team från 5 utvecklare, projekt på UIKit från 50 skärmar, testkrav över 80%, endast iOS (VIPER överförs inte till Android utan omskrivning). VIPER ger en förutsägbar struktur: en ny utvecklare förstår modulen inom 15 minuter. Utvecklingshastigheten är dock 20–30% lägre jämfört med MVVM på grund av det större antalet filer. På IT Sectr använder vi VIPER för enterprise-projekt på UIKit med team från 3 personer och föredrar Clean Architecture för nya projekt i SwiftUI.

Testning av VIPER-moduler

VIPER är designat för testning — varje komponent testas isolerat via protokoll. Interactor testas med mock-tjänster: det kontrolleras att fetchUser anropades med rätt ID och att resultatet skickades till Presenter. Presenter testas med mock-objekt av View och Interactor. Router testas med mock-navigering: det kontrolleras att navigateToProfile anropades med rätt userId och att rätt modul skapades. View testas med UI-tester (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-objekt för VIPER skapas manuellt (klass med lagrade capture-egenskaper) eller via biblioteken Cuckoo / Mockingbird. Manuella mock-klasser är enklare och mer förståeliga, särskilt för att lära nya utvecklare. Varje mock lagrar infångade värden (capturedUserId, displayedName) och anropsflaggor (didShowLoading). I slutet av testet kontrolleras inte bara att metoden anropades, utan också med vilka parametrar — detta ger förtroende för datas flödes korrekthet.

Kodtäckning i IT Sectr VIPER-projekt når 85–95% för Interactor, 90–95% för Presenter, 70–80% för Router, 30–50% för View (via UI-tester). View testas med skärmbildstester (SnapshotTesting, 1,5K stjärnor) — detta är snabbare än XCUITest och täcker fler fall. Den totala täckningen av ett VIPER-projekt är vanligtvis 70–80%, vilket är högre än ett MVVM-projekt (50–65%), men kräver mer tid för att skriva tester (30–40% av utvecklingstiden jämfört med 20–25% i MVVM).

Vanliga frågor

Hur många filer har en VIPER-modul?

Minst 11 filer: 5 protokoll (ViewProtocol, InteractorProtocol, PresenterProtocol, RouterProtocol, Entity), 5 implementeringar (ViewController, Interactor, Presenter, Router, Entity) och Builder/Assembler. Med Mapper (Formatter) — 12–13. För ett projekt med 50 skärmar är detta 550–650 filer endast för VIPER-moduler. MVVM kräver 3 filer per skärm (ViewModel, View, Model) — 150 filer för 50 skärmar.

Kan VIPER användas på Android?

Ja, teoretiskt är VIPER överförbart till Android, men i praktiken tillämpas det inte — Google rekommenderar MVVM med Jetpack. VIPER skapades för iOS UIKit, där ViewController är svårt att testa på grund av livscykeln. På Android löser Jetpack ViewModel testproblemet utan VIPER-isolering. Android-motsvarigheten till VIPER — Clean Architecture med uppdelning i module/feature.

Vad är skillnaden mellan VIPER och Clean Architecture?

VIPER är en iOS-specifik implementering av Clean Architecture. Interactor motsvarar Use Case, Entity — Domain Model, Presenter — Presentation-lagret. Clean Architecture lägger till ett Repository/Gateway-lager mellan Interactor och data, vilket i VIPER vanligtvis implementeras inuti Interactor. Clean Architecture föreskriver inte Router — navigering överlåts till implementeringen.

Behövs VIPER för SwiftUI-projekt?

Nej — SwiftUI är designat för MVVM + Combine. VIPER i SwiftUI är överdrivet: fem komponenter för en skärm med deklarativt UI är overhead utan fördel. För SwiftUI välj MVVM eller TCA (The Composable Architecture). VIPER förblir relevant för äldre UIKit-kod och projekt där iOS 12 och lägre är minimiversionen.

Hur skickar man data mellan VIPER-moduler?

Via Router. Modul A anropar router.navigateToProfile(userId: id). Router A skapar modul B via Builder, skickar userId. Återsändning — via delegat: modul B definierar protokollet ModuleBDelegate, modul A implementerar det och skickar via Router. Systemhändelser (utloggning) — via NotificationCenter.

Sammanfattning

  • VIPER — fem komponenter med strikt separation: View, Interactor, Presenter, Entity, Router
  • Modularitet — varje skärm är isolerad, Builder assemblerar beroenden via constructor injection
  • Router — flyttar navigering från Presenter, löser navigeringsproblemet på iOS
  • Interactor — ren affärslogik utan UIKit, testas med enhetstester
  • Kodmängd — 11+ filer per skärm, utveckling 20–30% långsammare än MVVM
  • Testning — 70–80% täckning, Interactor och Presenter testas via mock-objekt
  • SwiftUI vs UIKit — VIPER för UIKit (äldre kod), MVVM/TCA för SwiftUI

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också