VIPER: mga pangunahing konsepto, pattern na View-Interactor-Presenter-Entity-Router

May-akda: IT Sectr Nai-publish: 2026-02-16 Oras ng pagbabasa: 9 min

VIPER (View-Interactor-Presenter-Entity-Router) — modular na arkitektura na binuo sa kompanyang Mutual Mobile para sa mga iOS application. Hinahati ng VIPER ang application sa limang layer: View ang responsable sa pagpapakita, Interactor — sa lohika ng negosyo, Presenter — sa paghahanda ng datos, Entity — sa mga modelo ng datos, Router — sa nabigasyon sa pagitan ng mga modyul. Ang VIPER ay ang pinakadetalyadong implementasyon ng prinsipyo ng iisang responsibilidad sa mga mobile na arkitektura. Higit pa — sa artikulo sa objc.io.

Mga Pangunahing Punto

  • VIPER — limang bahagi: View, Interactor, Presenter, Entity, Router na may malinaw na hangganan ng responsibilidad
  • Modularidad — bawat screen (modyul) ay nakahiwalay, komunikasyon sa pamamagitan ng mga protokol
  • Router — inaalis ang nabigasyon mula sa Presenter, nilulutas ang problema sa nabigasyon ng iOS
  • Interactor — naglalaman ng lohika ng negosyo at hindi nakadepende sa UIKit, sinusuri ng mga unit test
  • iOS-native — ginawa ang VIPER para sa UIKit bago dumating ang SwiftUI at nananatiling pamantayan para sa malalaking proyekto sa iOS

Ano ang VIPER: limang bahagi ng modular na arkitektura

VIPER (View-Interactor-Presenter-Entity-Router) — isang pattern ng arkitektura na binuo noong 2013–2014 sa kompanyang Mutual Mobile para sa malalaking proyekto sa iOS. Bawat screen ng application ay isang hiwalay na modyul ng limang bahagi na may mahigpit na tinukoy na mga responsibilidad. Ang VIPER ay ang pinakamahigpit na implementasyon ng prinsipyo ng iisang responsibilidad (Single Responsibility Principle) sa mobile development: walang bahagi ang gumagawa ng kayang gawin ng iba.

View — passive na bahagi na responsable lamang sa pagpapakita ng datos na ipinadala ng Presenter. Ang View ay hindi naglalaman ng lohika ng negosyo, hindi humahawak ng nabigasyon, hindi gumagawa ng mga network request. Sa iOS — UIViewController na may protokol na ViewProtocol. Interactor — layer ng lohika ng negosyo na gumagana sa Entity at mga serbisyo (network, database, GPS). Hindi ini-import ng Interactor ang UIKit. Presenter — tagapamagitan sa pagitan ng View at Interactor: tumatanggap ng datos mula sa Interactor, nagfo-format para sa pagpapakita, ipinapadala sa View. Hindi rin ini-import ng Presenter ang UIKit. Entity — mga modelo ng datos (struct, class). Router — namamahala ng nabigasyon: gumagawa ng mga modyul, nagbubukas ng mga screen, nagpapadala ng datos sa pagitan ng mga modyul.

BahagiResponsibilidadMga dependency
ViewPagpapakita, animasyon, kilosUIKit (View lang)
InteractorLohika ng negosyo, network, databaseEntity, mga serbisyo
PresenterPag-format ng datos, mga utos ng ViewViewProtocol, Interactor
EntityMga modelo ng datosWala
RouterNabigasyon, paggawa ng modyulUIViewController (para sa transisyon)

Mga koneksyon sa pagitan ng mga bahagi ay inilalarawan ng mga protokol. Tinutukoy ng ViewProtocol ang mga pamamaraan ng pagpapakita, InteractorProtocol — mga pamamaraan ng lohika ng negosyo, PresenterProtocol — mga pamamaraan ng paghawak ng kaganapan, RouterProtocol — mga pamamaraan ng nabigasyon. Bawat bahagi ay nakikipag-ugnayan sa isa pa sa pamamagitan lamang ng protokol, na nagpapahintulot ng madaling pagpapalit ng mga implementasyon at nakahiwalay na pagsubok. Ang karaniwang modyul ng VIPER mula sa isang screen ay naglalaman ng 5 protokol + 5 klase + 1 Builder/Assembler = 11 file bawat screen.

VIPER sa Swift: modyul, Router at Presenter

Pagbuo ng modyul ng VIPER ay isinasagawa sa Builder (o Assembler), na lumilikha ng lahat ng limang bahagi at iniuugnay ang mga ito sa pamamagitan ng mga protokol. Ang Builder ay ang tanging lugar kung saan alam ng mga bahagi ang mga kongkretong uri ng bawat isa. Pagkatapos ng pagbuo, ang View ay ibabalik sa labas para sa pagpapakita, ang natitirang chain ay malinis at sinusuri nang nakahiwalay.

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

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

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

// Interactor — lohika ng negosyo
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 — paghahanda ng datos
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):
                // paghawak ng error
            }
        }
    }
}

// Router — nabigasyon
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 — pagbuo ng modyul
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 — ang pangunahing elemento ng VIPER na nagpapatupad ng manu-manong Dependency Injection. Ang pag-iniksyon ng dependency sa pamamagitan ng constructor (constructor injection) ay ginagarantiyang hindi malilikha ang isang bahagi nang wala ang mga dependency nito. Sa modernong VIPER, ang Builder ay maaaring gumamit ng Swinject (DI container), ngunit ang manu-manong pagbuo ay nananatiling mas transparent para sa pagsubok. Sa IT Sectr inilalapat namin ang VIPER na may manu-manong pagbuo para sa mga modyul na may kumplikadong lohika — pinapasimple nito ang pagbabasa ng code para sa mga bagong developer.

Mapper (Formatter) — isang opsyonal na ikaanim na bahagi ng VIPER. Ang Mapper ay nagko-convert ng Entity (mga modelo ng database/server) sa ViewModel (mga modelo ng pagpapakita). Ang Entity ay naglalaman ng UserDTO na may mga field na id, first_name, last_name, email. ViewModel — UserDisplayItem na may name (first_name + last_name) at email. Ang Mapper ay isinasagawa sa Presenter. Kung kumplikado ang pagmamapa ng datos (maramihang Entity → isang ViewModel), ang Mapper ay inililipat sa isang hiwalay na klase para sa pagsubok.

Komunikasyon sa pagitan ng mga modyul ng VIPER

Ang mga modyul ng VIPER ay nakahiwalay at hindi alam ang tungkol sa isa't isa. Ang komunikasyon sa pagitan ng mga modyul ay nangyayari sa pamamagitan ng Router. Kapag pinindot ng gumagamit ang button na „Profil" sa screen ng gumagamit, tinatawag ng Presenter ang router.navigateToProfile(userId: 42). Gumagawa ang Router ng bagong modyul sa pamamagitan ng ProfileModuleBuilder.build(userId: 42) at bubuksan ito sa pamamagitan ng navigationController.push. Daloy ng datos: Modyul A → Router A → Modyul B Builder → Modyul B ay ginawa at binuksan.

Pagpapadala ng datos pabalik (halimbawa, napili ang isang lungsod sa screen ng pagpili → ibinalik sa screen ng pag-edit ng profile) sa VIPER ay ipinapatupad sa pamamagitan ng mga delegado o closure. Tinutukoy ng Modyul B ang protokol na ModuleBDelegate na may pamamaraang didSelectCity(_ city: City). Ipinapatupad ng Modyul A ang protokol na ito. Ipinapadala ng Router A ang delegado sa Module B Builder. Sa pagpili ng lungsod, tinatawag ng Modyul B ang delegate?.didSelectCity(city). Ito ay isang pamantayang kasanayan sa iOS, pamilyar sa bawat developer ng UIKit.

ScenarioMekanismoHalimbawa
Transisyon pasulongRouter → BuildernavigateToProfile(userId:)
Pagpapadala ng datos pabalikDelegatedidSelectCity(_:)
Notipikasyon ng sistemaNotificationCenterUserDidLogout
Kaganapan mula sa InteractorPresenter → ViewWebSocket message

NotificationCenter ay ginagamit para sa mga kaganapan ng sistema (pag-logout, pagbabago ng taripa, push notipikasyon) na sabay na nakakaapekto sa maraming modyul. Ang Router o AppDelegate ay nag-subscribe sa Notification, gumagawa ng kinakailangang modyul o nag-a-update ng estado. Hindi pinagbabawalan ng VIPER ang NotificationCenter — mahalaga na ito ay gamitin lamang para sa mga kaganapang 1-sa-marami, at para sa komunikasyong 1-sa-1 ay gumamit ng mga delegado o closure.

Paghahambing ng VIPER sa MVVM at Clean Architecture

VIPER vs MVVM — Ang VIPER ay nangangailangan ng 2–3 beses na mas maraming code bawat screen, ngunit nagbibigay ng ganap na paghihiwalay ng mga bahagi. Ang MVVM na may ViewModel + SwiftUI ay mas simple at mas mabilis, ngunit mas mahina ang pag-scale sa mga team na 5+ developer. Mahigpit na tinutukoy ng VIPER kung sino ang responsable para sa ano: Interactor — lohika ng negosyo lamang, Presenter — pag-format, Router — nabigasyon. Sa MVVM, ang ViewModel ay madalas lumalaki, na inaako ang nabigasyon at lohika ng negosyo.

VIPER vs Clean Architecture — Ang VIPER ay isang partikular na kaso ng Clean Architecture, na inangkop para sa iOS UIKit. Interactor = Use Case, Entity = Domain Model, Presenter = Presentation, Router = Controller sa terminolohiya ni Robert Martin. Ang Clean Architecture ay nagdaragdag ng layer ng Gateway/Repository sa pagitan ng Interactor at datos, na sa VIPER ay karaniwang hindi pinaghihiwalay. Para sa mga modernong proyekto sa SwiftUI, karamihan ng mga team ay pumipili ng Clean Architecture (The Composable Architecture) o MVVM, iniiwan ang VIPER para sa legacy code sa UIKit.

Kailan pumili ng VIPER — mga team mula 5 developer, proyekto sa UIKit mula 50 screen, mga kinakailangan sa pagsubok na higit sa 80%, iOS lamang (hindi naililipat ang VIPER sa Android nang walang pagsusulat muli). Nagbibigay ang VIPER ng predictable na istraktura: nauunawaan ng bagong developer ang modyul sa loob ng 15 minuto. Gayunpaman, ang bilis ng pag-develop ay 20–30% na mas mababa kumpara sa MVVM dahil sa mas maraming bilang ng mga file. Sa IT Sectr ginagamit namin ang VIPER para sa enterprise na mga proyekto sa UIKit na may mga team mula 3 tao at mas pinipili ang Clean Architecture para sa mga bagong proyekto sa SwiftUI.

Pagsubok ng mga modyul ng VIPER

Ang VIPER ay dinisenyo para sa pagsubok — bawat bahagi ay sinusuri nang nakahiwalay sa pamamagitan ng mga protokol. Ang Interactor ay sinusuri gamit ang mga mock service: sinusuri kung ang fetchUser ay tinawag na may tamang ID at kung ang resulta ay ipinadala sa Presenter. Ang Presenter ay sinusuri gamit ang mga mock na bagay ng View at Interactor. Ang Router ay sinusuri gamit ang mock nabigasyon: sinusuri kung ang navigateToProfile ay tinawag na may tamang userId at kung ang tamang modyul ay ginawa. Ang View ay sinusuri gamit ang mga UI test (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)
    }
}

Mga mock na bagay para sa VIPER ay ginagawa nang manu-mano (klase na may mga nakaimbak na capture property) o sa pamamagitan ng mga library na Cuckoo / Mockingbird. Ang mga manual na mock na klase ay mas simple at mas madaling maunawaan, lalo na para sa pagsasanay ng mga bagong developer. Bawat mock ay nag-iimbak ng mga nakuha na halaga (capturedUserId, displayedName) at mga flag ng tawag (didShowLoading). Sa dulo ng pagsubok, sinusuri hindi lamang kung ang pamamaraan ay tinawag, kundi pati na rin kung anong mga parameter ang ginamit — ito ay nagbibigay ng kumpyansa sa kawastuhan ng daloy ng datos.

Saklaw ng code sa mga proyektong VIPER ng IT Sectr ay umaabot sa 85–95% para sa Interactor, 90–95% para sa Presenter, 70–80% para sa Router, 30–50% para sa View (sa pamamagitan ng UI test). Ang View ay sinusuri gamit ang mga screenshot test (SnapshotTesting, 1.5K bituin) — ito ay mas mabilis kaysa sa XCUITest at sumasaklaw ng mas maraming kaso. Ang kabuuang saklaw ng proyektong VIPER ay karaniwang 70–80%, na mas mataas kaysa sa proyektong MVVM (50–65%), ngunit nangangailangan ng mas maraming oras sa pagsulat ng mga test (30–40% ng oras ng pag-develop kumpara sa 20–25% sa MVVM).

Mga Madalas Itanong

Ilang file mayroon ang isang modyul ng VIPER?

Minimum na 11 file: 5 protokol (ViewProtocol, InteractorProtocol, PresenterProtocol, RouterProtocol, Entity), 5 implementasyon (ViewController, Interactor, Presenter, Router, Entity) at Builder/Assembler. May Mapper (Formatter) — 12–13. Para sa proyektong may 50 screen, ito ay 550–650 file para lamang sa mga modyul ng VIPER. Ang MVVM ay nangangailangan ng 3 file bawat screen (ViewModel, View, Model) — 150 file para sa 50 screen.

Maaari bang gamitin ang VIPER sa Android?

Oo, sa teorya ay naililipat ang VIPER sa Android, ngunit sa praktika ay hindi ginagamit — inirerekomenda ng Google ang MVVM na may Jetpack. Ginawa ang VIPER para sa iOS UIKit, kung saan ang ViewController ay mahirap subukan dahil sa siklo ng buhay. Sa Android, nilulutas ng Jetpack ViewModel ang problema sa pagsubok nang walang paghihiwalay ng VIPER. Ang katumbas ng VIPER sa Android — Clean Architecture na may paghahati sa module/feature.

Ano ang pagkakaiba ng VIPER at Clean Architecture?

Ang VIPER ay isang implementasyon ng Clean Architecture na tiyak sa iOS. Ang Interactor ay tumutugma sa Use Case, Entity — Domain Model, Presenter — layer ng Presentation. Ang Clean Architecture ay nagdaragdag ng Gateway/Repository sa pagitan ng Interactor at datos, na sa VIPER ay karaniwang ipinapatupad sa loob ng Interactor. Hindi nagtatakda ang Clean Architecture ng Router — ang nabigasyon ay naiwan sa pagpapasya ng implementasyon.

Kailangan ba ang VIPER para sa mga proyektong SwiftUI?

Hindi — ang SwiftUI ay dinisenyo para sa MVVM + Combine. Ang VIPER sa SwiftUI ay labis: limang bahagi para sa isang screen na may deklaratibong UI ay overhead na walang pakinabang. Para sa SwiftUI pumili ng MVVM o TCA (The Composable Architecture). Ang VIPER ay nananatiling may kaugnayan para sa legacy code ng UIKit at mga proyekto kung saan ang iOS 12 at mas mababa ay ang pinakamababang bersyon.

Paano magpadala ng datos sa pagitan ng mga modyul ng VIPER?

Sa pamamagitan ng Router. Ang Modyul A ay tumatawag ng router.navigateToProfile(userId: id). Gumagawa ang Router A ng modyul B sa pamamagitan ng Builder, ipinapadala ang userId. Pagpapadala pabalik — sa pamamagitan ng delegado: tinutukoy ng Modyul B ang protokol na ModuleBDelegate, ipinapatupad ito ng Modyul A at ipinapadala sa pamamagitan ng Router. Mga kaganapan ng sistema (pag-logout) — sa pamamagitan ng NotificationCenter.

Buod

  • VIPER — limang bahagi na may mahigpit na paghihiwalay: View, Interactor, Presenter, Entity, Router
  • Modularidad — bawat screen ay nakahiwalay, Binubuo ng Builder ang mga dependency sa pamamagitan ng constructor injection
  • Router — inaalis ang nabigasyon mula sa Presenter, nilulutas ang problema sa nabigasyon sa iOS
  • Interactor — purong lohika ng negosyo na walang UIKit, sinusuri ng mga unit test
  • Dami ng code — 11+ file bawat screen, pag-develop 20–30% mas mabagal kaysa sa MVVM
  • Pagsubok — 70–80% saklaw, Interactor at Presenter ay sinusuri sa pamamagitan ng mga mock na bagay
  • SwiftUI vs UIKit — VIPER para sa UIKit (legacy code), MVVM/TCA para sa SwiftUI

Gagawa kami ng mobile application na turnkey

Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.

Pag-usapan ang proyekto

Basahin din