VIPER (View-Interactor-Presenter-Entity-Router) — arsitektur modular yang dikembangkan di perusahaan Mutual Mobile untuk aplikasi iOS. VIPER membagi aplikasi menjadi lima lapisan: View bertanggung jawab untuk menampilkan, Interactor — untuk logika bisnis, Presenter — untuk persiapan data, Entity — untuk model data, Router — untuk navigasi antar modul. VIPER adalah implementasi paling detail dari prinsip tanggung jawab tunggal di antara arsitektur mobile. Selengkapnya — di artikel di objc.io.
Poin Utama
VIPER (View-Interactor-Presenter-Entity-Router) — pola arsitektur yang dikembangkan pada 2013–2014 di perusahaan Mutual Mobile untuk proyek iOS besar. Setiap layar aplikasi adalah modul terpisah dari lima komponen dengan tanggung jawab yang ditentukan secara ketat. VIPER adalah implementasi paling ketat dari prinsip tanggung jawab tunggal (Single Responsibility Principle) dalam pengembangan mobile: tidak ada komponen yang melakukan apa yang bisa dilakukan komponen lain.
View — komponen pasif yang hanya bertanggung jawab untuk menampilkan data yang dikirim oleh Presenter. View tidak berisi logika bisnis, tidak menangani navigasi, tidak melakukan permintaan jaringan. Di iOS — UIViewController dengan protokol ViewProtocol. Interactor — lapisan logika bisnis yang bekerja dengan Entity dan layanan (jaringan, database, GPS). Interactor tidak mengimpor UIKit. Presenter — perantara antara View dan Interactor: menerima data dari Interactor, memformat untuk ditampilkan, mengirimkan ke View. Presenter juga tidak mengimpor UIKit. Entity — model data (struct, class). Router — mengelola navigasi: membuat modul, membuka layar, mengirimkan data antar modul.
| Komponen | Tanggung Jawab | Ketergantungan |
|---|---|---|
| View | Menampilkan, animasi, gestur | UIKit (hanya View) |
| Interactor | Logika bisnis, jaringan, database | Entity, layanan |
| Presenter | Pemformatan data, perintah View | ViewProtocol, Interactor |
| Entity | Model data | Tidak ada |
| Router | Navigasi, pembuatan modul | UIViewController (untuk transisi) |
Hubungan antar komponen dijelaskan oleh protokol. ViewProtocol mendefinisikan metode tampilan, InteractorProtocol — metode logika bisnis, PresenterProtocol — metode penanganan peristiwa, RouterProtocol — metode navigasi. Setiap komponen berkomunikasi dengan yang lain hanya melalui protokol, yang memungkinkan penggantian implementasi dengan mudah dan pengujian terisolasi. Rata-rata modul VIPER dari satu layar berisi 5 protokol + 5 kelas + 1 Builder/Assembler = 11 file per layar.
Pembangunan modul VIPER dilakukan di Builder (atau Assembler), yang membuat semua lima komponen dan menghubungkannya melalui protokol. Builder adalah satu-satunya tempat di mana komponen saling mengetahui tipe konkret satu sama lain. Setelah pembangunan, View dikembalikan ke luar untuk ditampilkan, sisanya bersih dan diuji secara terisolasi.
// 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 — logika bisnis
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 — persiapan data
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):
// penanganan kesalahan
}
}
}
}
// Router — navigasi
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 — pembangunan modul
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 — elemen kunci VIPER yang mengimplementasikan injeksi ketergantungan manual (Dependency Injection). Injeksi ketergantungan melalui konstruktor (constructor injection) menjamin bahwa komponen tidak dapat dibuat tanpa ketergantungannya. Dalam VIPER modern, Builder dapat menggunakan Swinject (kontainer DI), tetapi pembangunan manual tetap lebih transparan untuk pengujian. Di IT Sectr kami menerapkan VIPER dengan pembangunan manual untuk modul dengan logika kompleks — ini menyederhanakan pembacaan kode oleh pengembang baru.
Mapper (Formatter) — komponen keenam opsional VIPER. Mapper mengubah Entity (model database/server) menjadi ViewModel (model tampilan). Entity berisi UserDTO dengan bidang id, first_name, last_name, email. ViewModel — UserDisplayItem dengan name (first_name + last_name) dan email. Mapper dijalankan di Presenter. Jika pemetaan data kompleks (beberapa Entity → satu ViewModel), Mapper dipindahkan ke kelas terpisah untuk pengujian.
Modul VIPER terisolasi dan tidak saling mengetahui. Komunikasi antar modul terjadi melalui Router. Ketika pengguna menekan tombol „Profil" di layar pengguna, Presenter memanggil router.navigateToProfile(userId: 42). Router membuat modul baru melalui ProfileModuleBuilder.build(userId: 42) dan membukanya melalui navigationController.push. Alur data: Modul A → Router A → Modul B Builder → Modul B dibuat dan dibuka.
Mengirimkan data kembali (misalnya, kota dipilih di layar pemilihan → dikembalikan ke layar edit profil) di VIPER diimplementasikan melalui delegasi atau closure. Modul B mendefinisikan protokol ModuleBDelegate dengan metode didSelectCity(_ city: City). Modul A mengimplementasikan protokol ini. Router A mengirimkan delegasi ke Module B Builder. Saat memilih kota, Modul B memanggil delegate?.didSelectCity(city). Ini adalah praktik standar iOS yang dikenal oleh setiap pengembang UIKit.
| Skenario | Mekanisme | Contoh |
|---|---|---|
| Transisi maju | Router → Builder | navigateToProfile(userId:) |
| Mengirim data kembali | Delegate | didSelectCity(_:) |
| Notifikasi sistem | NotificationCenter | UserDidLogout |
| Peristiwa dari Interactor | Presenter → View | WebSocket message |
NotificationCenter digunakan untuk peristiwa sistem (logout, perubahan tarif, notifikasi push) yang memengaruhi beberapa modul secara bersamaan. Router atau AppDelegate berlangganan Notification, membuat modul yang diperlukan atau memperbarui status. VIPER tidak melarang NotificationCenter — yang penting adalah hanya digunakan untuk peristiwa 1-ke-banyak, dan untuk komunikasi 1-ke-1 digunakan delegasi atau closure.
VIPER vs MVVM — VIPER membutuhkan 2–3 kali lebih banyak kode per layar, tetapi memberikan isolasi absolut komponen. MVVM dengan ViewModel + SwiftUI lebih sederhana dan lebih cepat, tetapi kurang berskala pada tim 5+ pengembang. VIPER secara ketat menentukan siapa bertanggung jawab untuk apa: Interactor — hanya logika bisnis, Presenter — pemformatan, Router — navigasi. Di MVVM, ViewModel sering membesar, mengambil alih navigasi dan logika bisnis.
VIPER vs Clean Architecture — VIPER adalah kasus khusus dari Clean Architecture, yang diadaptasi untuk iOS UIKit. Interactor = Use Case, Entity = Domain Model, Presenter = Presentation, Router = Controller dalam terminologi Robert Martin. Clean Architecture menambahkan lapisan Gateway/Repository antara Interactor dan data, yang dalam VIPER biasanya tidak dipisahkan. Untuk proyek modern di SwiftUI, sebagian besar tim memilih Clean Architecture (The Composable Architecture) atau MVVM, meninggalkan VIPER untuk kode lama di UIKit.
Kapan memilih VIPER — tim dari 5 pengembang, proyek UIKit dari 50 layar, persyaratan pengujian di atas 80%, hanya iOS (VIPER tidak dipindahkan ke Android tanpa penulisan ulang). VIPER memberikan struktur yang dapat diprediksi: pengembang baru memahami modul dalam 15 menit. Namun kecepatan pengembangan 20–30% lebih rendah dibandingkan MVVM karena jumlah file yang lebih banyak. Di IT Sectr kami menggunakan VIPER untuk proyek enterprise di UIKit dengan tim dari 3 orang dan lebih memilih Clean Architecture untuk proyek baru di SwiftUI.
VIPER dirancang untuk pengujian — setiap komponen diuji secara terisolasi melalui protokol. Interactor diuji dengan layanan mock: diperiksa apakah fetchUser dipanggil dengan ID yang benar dan apakah hasilnya dikirim ke Presenter. Presenter diuji dengan objek mock View dan Interactor. Router diuji dengan navigasi mock: diperiksa apakah navigateToProfile dipanggil dengan userId yang benar dan apakah modul yang benar dibuat. View diuji dengan tes UI (XCUITest).
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)
}
}
Objek mock untuk VIPER dibuat secara manual (kelas dengan properti capture yang disimpan) atau melalui pustaka Cuckoo / Mockingbird. Kelas mock manual lebih sederhana dan lebih mudah dipahami, terutama untuk melatih pengembang baru. Setiap mock menyimpan nilai yang ditangkap (capturedUserId, displayedName) dan bendera panggilan (didShowLoading). Di akhir tes diperiksa tidak hanya apakah metode dipanggil, tetapi juga dengan parameter apa — ini memberikan keyakinan akan kebenaran aliran data.
Cakupan kode di proyek VIPER IT Sectr mencapai 85–95% untuk Interactor, 90–95% untuk Presenter, 70–80% untuk Router, 30–50% untuk View (melalui tes UI). View diuji dengan tes tangkapan layar (SnapshotTesting, 1,5K bintang) — ini lebih cepat dari XCUITest dan mencakup lebih banyak kasus. Cakupan total proyek VIPER biasanya 70–80%, lebih tinggi dari proyek MVVM (50–65%), tetapi membutuhkan lebih banyak waktu untuk menulis tes (30–40% dari waktu pengembangan dibandingkan 20–25% di MVVM).
Pertanyaan yang Sering Diajukan
Minimal 11 file: 5 protokol (ViewProtocol, InteractorProtocol, PresenterProtocol, RouterProtocol, Entity), 5 implementasi (ViewController, Interactor, Presenter, Router, Entity) dan Builder/Assembler. Dengan Mapper (Formatter) — 12–13. Untuk proyek dengan 50 layar, ini berarti 550–650 file hanya untuk modul VIPER. MVVM membutuhkan 3 file per layar (ViewModel, View, Model) — 150 file untuk 50 layar.
Ya, secara teoretis VIPER dapat dipindahkan ke Android, tetapi dalam praktiknya tidak diterapkan — Google merekomendasikan MVVM dengan Jetpack. VIPER dibuat untuk iOS UIKit, di mana ViewController sulit diuji karena siklus hidup. Di Android, Jetpack ViewModel memecahkan masalah pengujian tanpa isolasi VIPER. Padanan Android dari VIPER — Clean Architecture dengan pembagian menjadi module/feature.
VIPER adalah implementasi spesifik iOS dari Clean Architecture. Interactor sesuai dengan Use Case, Entity — Domain Model, Presenter — lapisan Presentation. Clean Architecture menambahkan lapisan Repository/Gateway antara Interactor dan data, yang dalam VIPER biasanya diimplementasikan di dalam Interactor. Clean Architecture tidak menentukan Router — navigasi diserahkan pada keputusan implementasi.
Tidak — SwiftUI dirancang untuk MVVM + Combine. VIPER di SwiftUI berlebihan: lima komponen untuk satu layar dengan UI deklaratif adalah overhead tanpa manfaat. Untuk SwiftUI pilih MVVM atau TCA (The Composable Architecture). VIPER tetap relevan untuk kode lama UIKit dan proyek di mana iOS 12 dan lebih rendah adalah versi minimum.
Melalui Router. Modul A memanggil router.navigateToProfile(userId: id). Router A membuat modul B melalui Builder, mengirimkan userId. Pengiriman kembali — melalui delegasi: modul B mendefinisikan protokol ModuleBDelegate, modul A mengimplementasikannya dan mengirimkannya melalui Router. Peristiwa sistem (logout) — melalui NotificationCenter.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga