VIPER (View-Interactor-Presenter-Entity-Router) — Mutual Mobile kompaniyasi tomonidan iOS ilovalari uchun ishlab chiqilgan modul arxitekturasi. VIPER ilovani besh qatlamga ajratadi: View ko'rsatish uchun, Interactor — biznes mantiqi uchun, Presenter — ma'lumotlarni tayyorlash uchun, Entity — ma'lumot modellari uchun, Router — modullar orasidagi navigatsiya uchun javob beradi. VIPER mobil arxitekturalar orasida yagona javobgarlik tamoyilining eng batafsil amalga oshirilishidir. Batafsil — objc.io saytidagi maqolada.
Asosiy fikrlar
VIPER (View-Interactor-Presenter-Entity-Router) — 2013–2014 yillarda Mutual Mobile kompaniyasi tomonidan yirik iOS loyihalari uchun ishlab chiqilgan arxitektura namunasi. Ilovaning har bir ekrani qat'iy belgilangan vazifalarga ega besh komponentdan iborat alohida moduldir. VIPER mobil ishlab chiqishda yagona javobgarlik tamoyilining (Single Responsibility Principle) eng qat'iy amalga oshirilishidir: hech bir komponent boshqasi qila oladigan ishni bajarmaydi.
View — faqat Presenter tomonidan uzatilgan ma'lumotlarni ko'rsatish uchun javobgar bo'lgan passiv komponent. View biznes mantiqini o'z ichiga olmaydi, navigatsiyani boshqarmaydi, tarmoq so'rovlarini yubormaydi. iOS da — ViewProtocol protokoli bilan UIViewController. Interactor — Entity va xizmatlar (tarmoq, ma'lumotlar bazasi, GPS) bilan ishlaydigan biznes mantiqi qatlami. Interactor UIKit ni import qilmaydi. Presenter — View va Interactor o'rtasidagi vositachi: Interactor dan ma'lumotlarni oladi, ko'rsatish uchun formatlaydi, View ga uzatadi. Presenter ham UIKit ni import qilmaydi. Entity — ma'lumot modellari (struct, class). Router — navigatsiyani boshqaradi: modullarni yaratadi, ekranlarni ochadi, modullar orasida ma'lumotlarni uzatadi.
| Komponent | Javobgarlik | Bog'liqliklar |
|---|---|---|
| View | Ko'rsatish, animatsiyalar, imo-ishoralar | UIKit (faqat View) |
| Interactor | Biznes mantiqi, tarmoq, ma'lumotlar bazasi | Entity, xizmatlar |
| Presenter | Ma'lumotlarni formatlash, View buyruqlari | ViewProtocol, Interactor |
| Entity | Ma'lumot modellari | Yo'q |
| Router | Navigatsiya, modullarni yaratish | UIViewController (o'tishlar uchun) |
Komponentlar orasidagi aloqalar protokollar orqali tavsiflanadi. ViewProtocol ko'rsatish metodlarini, InteractorProtocol — biznes mantiqi metodlarini, PresenterProtocol — hodisalarni qayta ishlash metodlarini, RouterProtocol — navigatsiya metodlarini belgilaydi. Har bir komponent boshqasi bilan faqat protokol orqali muloqot qiladi, bu esa amalga oshirishlarni oson almashtirish va izolyatsiya qilingan test qilish imkonini beradi. Bitta ekranli VIPER moduli o'rtacha 5 protokol + 5 sinf + 1 Builder/Assembler = ekran uchun 11 faylni o'z ichiga oladi.
VIPER modulini qurish Builder (yoki Assembler) tomonidan amalga oshiriladi, u barcha besh komponentni yaratadi va ularni protokollar orqali bog'laydi. Builder — komponentlar bir-birining aniq turlarini biladigan yagona joy. Qurilishdan so'ng View ko'rsatish uchun tashqariga qaytariladi, qolgan zanjir toza va izolyatsiya qilingan holda test qilinadi.
// 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 — biznes mantiqi
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 — ma'lumotlarni tayyorlash
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):
// xatoni qayta ishlash
}
}
}
}
// Router — navigatsiya
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 — modulni qurish
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 — bog'liqliklarni qo'lda kiritishni (Dependency Injection) amalga oshiruvchi VIPER ning asosiy elementi. Konstruktor orqali bog'liqliklarni kiritish (constructor injection) komponentning o'z bog'liqliklarisiz yaratilmasligini kafolatlaydi. Zamonaviy VIPER da Builder Swinject (DI konteyner) dan foydalanishi mumkin, ammo qo'lda qurish test uchun shaffofroq qoladi. IT Sectr da biz murakkab mantiqli modullar uchun qo'lda qurish bilan VIPER ni qo'llaymiz — bu yangi dasturchilar uchun kodni o'qishni osonlashtiradi.
Mapper (Formatter) — VIPER ning ixtiyoriy oltinchi komponenti. Mapper Entity (ma'lumotlar bazasi/server modellari) ni ViewModel (ko'rsatish modellari) ga aylantiradi. Entity id, first_name, last_name, email maydonlari bilan UserDTO ni o'z ichiga oladi. ViewModel — name (first_name + last_name) va email bilan UserDisplayItem. Mapper Presenter da bajariladi. Ma'lumotlarni xaritalash murakkab bo'lsa (bir nechta Entity → bitta ViewModel), Mapper test qilish uchun alohida sinfga chiqariladi.
VIPER modullari izolyatsiya qilingan va bir-biri haqida bilmaydi. Modullar orasidagi aloqa Router orqali amalga oshiriladi. Foydalanuvchi foydalanuvchi ekranidagi „Profil" tugmasini bosganda, Presenter router.navigateToProfile(userId: 42) ni chaqiradi. Router ProfileModuleBuilder.build(userId: 42) orqali yangi modul yaratadi va uni navigationController.push orqali ochadi. Ma'lumot oqimi: Modul A → Router A → Modul B Builder → Modul B yaratiladi va ochiladi.
Ma'lumotlarni orqaga uzatish (masalan, tanlash ekranida shahar tanlandi → profilni tahrirlash ekraniga qaytarildi) VIPER da delegatlar yoki yopilishlar (closure) orqali amalga oshiriladi. Modul B didSelectCity(_ city: City) metodi bilan ModuleBDelegate protokolini belgilaydi. Modul A ushbu protokolni amalga oshiradi. Router A delegatni Module B Builder ga uzatadi. Shahar tanlanganda Modul B delegate?.didSelectCity(city) ni chaqiradi. Bu har bir UIKit dasturchisiga tanish bo'lgan standart iOS amaliyotidir.
| Ssenariy | Mexanizm | Misol |
|---|---|---|
| Oldinga o'tish | Router → Builder | navigateToProfile(userId:) |
| Ma'lumotni orqaga uzatish | Delegate | didSelectCity(_:) |
| Tizim bildirishnomasi | NotificationCenter | UserDidLogout |
| Interactor dan hodisa | Presenter → View | WebSocket message |
NotificationCenter bir vaqtning o'zida bir nechta modulga ta'sir qiladigan tizim hodisalari (chiqish, tarif o'zgarishi, push bildirishnomalari) uchun ishlatiladi. Router yoki AppDelegate Notification ga obuna bo'ladi, kerakli modulni yaratadi yoki holatni yangilaydi. VIPER NotificationCenter ni taqiqlamaydi — muhimi, u faqat 1-to-many hodisalari uchun ishlatilishi, 1-to-1 aloqa uchun esa delegatlar yoki yopilishlar qo'llanilishi kerak.
VIPER vs MVVM — VIPER ekran uchun 2–3 barobar ko'p kod talab qiladi, lekin komponentlarning mutlaq izolyatsiyasini ta'minlaydi. ViewModel + SwiftUI bilan MVVM sodda va tezroq, ammo 5+ dasturchidan iborat jamoalarda yomonroq masshtablanadi. VIPER kim nima uchun javobgar ekanligini qat'iy belgilaydi: Interactor — faqat biznes mantiqi, Presenter — formatlash, Router — navigatsiya. MVVM da ViewModel ko'pincha kattalashib, navigatsiya va biznes mantiqini o'ziga oladi.
VIPER vs Clean Architecture — VIPER iOS UIKit uchun moslashtirilgan Clean Architecture ning alohida holatidir. Interactor = Use Case, Entity = Domain Model, Presenter = Presentation, Router = Controller Robert Martin terminlarida. Clean Architecture Interactor va ma'lumotlar o'rtasida Gateway/Repository qatlamini qo'shadi, bu VIPER da odatda ajratilmaydi. SwiftUI dagi zamonaviy loyihalar uchun aksariyat jamoalar Clean Architecture (The Composable Architecture) yoki MVVM ni tanlab, VIPER ni UIKit dagi eski kod uchun qoldiradi.
VIPER ni qachon tanlash kerak — 5 dasturchidan iborat jamoalar, 50 ekrandan UIKit loyihasi, test talablari 80% dan yuqori, faqat iOS (VIPER qayta yozilmasdan Android ga o'tkazilmaydi). VIPER bashorat qilinadigan strukturani ta'minlaydi: yangi dasturchi modulni 15 daqiqada tushunadi. Biroq ishlab chiqish tezligi ko'proq fayllar soni sababli MVVM bilan solishtirganda 20–30% past. IT Sectr da biz 3 kishidan iborat jamoalar bilan UIKit dagi enterprise loyihalari uchun VIPER dan foydalanamiz va SwiftUI dagi yangi loyihalar uchun Clean Architecture ni afzal ko'ramiz.
VIPER test uchun mo'ljallangan — har bir komponent protokollar orqali izolyatsiya qilingan holda test qilinadi. Interactor mock-xizmatlar bilan test qilinadi: fetchUser ning to'g'ri ID bilan chaqirilgani va natijaning Presenter ga uzatilgani tekshiriladi. Presenter mock View va Interactor obyektlari bilan test qilinadi. Router mock-navigatsiya bilan test qilinadi: navigateToProfile ning to'g'ri userId bilan chaqirilgani va to'g'ri modul yaratilgani tekshiriladi. View UI testlari (XCUITest) bilan test qilinadi.
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-obyektlar VIPER uchun qo'lda (saqlanadigan capture xususiyatlariga ega sinf) yoki Cuckoo / Mockingbird kutubxonalari orqali yaratiladi. Qo'lda mock sinflari sodda va tushunarliroq, ayniqsa yangi dasturchilarni o'rgatish uchun. Har bir mock captured qiymatlarni (capturedUserId, displayedName) va chaqiruv bayroqlarini (didShowLoading) saqlaydi. Test oxirida metod nafaqat chaqirilganligi, balki qanday parametrlar bilan chaqirilganligi ham tekshiriladi — bu ma'lumot oqimining to'g'riligiga ishonch beradi.
Kod qamrovi IT Sectr ning VIPER loyihalarida Interactor uchun 85–95%, Presenter uchun 90–95%, Router uchun 70–80%, View uchun 30–50% (UI testlari orqali) ga etadi. View skrinshot testlari (SnapshotTesting, 1,5K yulduz) bilan test qilinadi — bu XCUITest dan tezroq va ko'proq holatlarni qamrab oladi. VIPER loyihasining umumiy qamrovi odatda 70–80% ni tashkil qiladi, bu MVVM loyihasidan (50–65%) yuqori, ammo test yozish uchun ko'proq vaqt talab etadi (rivojlanish vaqtining 30–40% i MVVM dagi 20–25% ga nisbatan).
Tez-tez so'raladigan savollar
Kamida 11 fayl: 5 protokol (ViewProtocol, InteractorProtocol, PresenterProtocol, RouterProtocol, Entity), 5 amalga oshirish (ViewController, Interactor, Presenter, Router, Entity) va Builder/Assembler. Mapper (Formatter) bilan — 12–13. 50 ekranli loyiha uchun bu faqat VIPER modullari uchun 550–650 fayl. MVVM ekran uchun 3 fayl talab qiladi (ViewModel, View, Model) — 50 ekran uchun 150 fayl.
Ha, nazariy jihatdan VIPER Android ga o'tkazilishi mumkin, ammo amalda qo'llanilmaydi — Google Jetpack bilan MVVM ni tavsiya qiladi. VIPER iOS UIKit uchun yaratilgan, bu yerda ViewController hayotiy tsikli tufayli test qilish qiyin. Android da Jetpack ViewModel VIPER izolyatsiyasisiz test muammosini hal qiladi. VIPER ning Android analogi — module/feature ga bo'lingan Clean Architecture.
VIPER iOS ga xos Clean Architecture amalga oshirilishidir. Interactor Use Case ga, Entity — Domain Model ga, Presenter — Presentation qatlamiga mos keladi. Clean Architecture Interactor va ma'lumotlar o'rtasida Repository/Gateway qo'shadi, bu VIPER da odatda Interactor ichida amalga oshiriladi. Clean Architecture Router ni belgilamaydi — navigatsiya amalga oshirish ixtiyoriga qoldiriladi.
Yo'q — SwiftUI MVVM + Combine uchun mo'ljallangan. SwiftUI da VIPER ortiqcha: deklarativ UI bilan bitta ekran uchun besh komponent foydasiz yuk. SwiftUI uchun MVVM yoki TCA (The Composable Architecture) ni tanlang. VIPER UIKit eski kodi va iOS 12 va undan past minimal versiyali loyihalar uchun dolzarbligicha qolmoqda.
Router orqali. Modul A router.navigateToProfile(userId: id) ni chaqiradi. Router A Builder orqali modul B ni yaratadi, userId ni uzatadi. Orqaga uzatish — delegat orqali: modul B ModuleBDelegate protokolini belgilaydi, modul A uni amalga oshiradi va Router orqali uzatadi. Tizim hodisalari (chiqish) — NotificationCenter orqali.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.