05. VIPER — Enterprise Modular Arxitektura
Arxitektura
iOS Arxitektura 5: VIPER — Enterprise-Grade Modular Arxitektura
Holat: 2026-yil oktabr holatiga ko'ra yozildi. Ushbu darsda VIPER arxitekturasining zamonaviy Swift 6 va iOS sharoitida keng ko'lamli (enterprise) loyihalarda qo'llanilishi, uning protokollarga asoslangan qat'iy tuzilmasi ko'rib chiqiladi.
Mundarija
- VIPER nima?
- VIPER ma'lumot oqimi (Data Flow)
- Har bir qism uchun protocol'lar
- Module Builder va Dependency Injection
- Amaliy loyiha: User Profile VIPER Module
- VIPER va UITableView (ViewModel ishlashi)
- VIPER afzalliklari
- VIPER kamchiliklari
- Xcode Template va Code Generator'lar
- VIPER vs MVVM vs MVC
- Xulosa
- Manbalar
1. VIPER nima? {#viper-nima}
VIPER — bu Clean Architecture (Toza Arxitektura) tamoyillariga asoslangan, iOS dasturlashda keng tarqalgan modul-arxitektura patterni. Har bir harf alohida mas'uliyatga ega bo'lgan qatlamni (komponentni) bildiradi:
- V (View): Foydalanuvchi interfeysi (UI).
UIViewControlleryoki SwiftUI view'laridan iborat. Faqat UI ni chizish va foydalanuvchi harakatlarini (bosish, surish) Presenter'ga yetkazish uchun javobgar. O'zida hech qanday biznes logika saqlamaydi. - I (Interactor): Biznes logika qatlami. Tarmoqdan (Network) yoki ma'lumotlar bazasidan (Database) ma'lumotlarni olish (use case'lar) va ularni qayta ishlash bilan shug'ullanadi. UIKit'dan butunlay mustaqil bo'lishi kerak.
- P (Presenter): View va Interactor orasidagi ko'prik. View'dan kelgan hodisalarni qabul qiladi, Interactor'ga buyruq beradi. Interactor'dan kelgan ma'lumotlarni View tushunadigan formatga (ViewModel) o'girib, View'ga uzatadi. Shuningdek, navigatsiya kerak bo'lsa Router'ga xabar beradi.
- E (Entity): Ma'lumot modellari (Data models/Structlar). Faqat Interactor va (ba'zan) Presenter tomonidan ishlatiladi. View Entity'ni umuman ko'rmasligi kerak.
- R (Router / Wireframe): Ekranlar orasidagi navigatsiya va qaysi ekranni qachon ochish kerakligini hal qiluvchi qatlam. Boshqa modullarni chaqirish vazifasi ham shunga yuklangan.
VIPER Arxitekturasi
Senior eslatmasi: VIPER — bu shunchaki arxitektura emas, u butun bir modul konseptidir. Bitta ekran (modul) uchun sizdan kamida 5-6 ta fayl yozish talab etiladi.
2. VIPER ma'lumot oqimi (Data Flow) {#viper-data-flow}
VIPER qatlamlari o'zaro to'g'ridan-to'g'ri bog'lanmaydi, ular asosan Protocol'lar orqali gaplashadi.
VIPER Ma'lumot Oqimi
Oqim ketma-ketligi (masalan, ro'yxatni yuklash):
- View ekranga chiqdi (
viewDidLoad). U Presenter'ga aytadi:presenter.viewDidLoad(). - Presenter Interactor'ga buyruq beradi:
interactor.fetchUsers(). - Interactor tarmoqdan (Service) ma'lumotlarni (Entity) oladi va Presenter'ga qaytaradi (Output protocol orqali).
- Presenter olingan Entity'larni View uchun qulay bo'lgan ViewModel'larga aylantiradi va View'ga yuboradi:
view.showUsers(viewModels). - Foydalanuvchi biror foydalanuvchini bossa, View yana Presenter'ga xabar beradi, Presenter esa Router'ga
router.navigateToDetail(user)deydi.
3. Har bir qism uchun protocol'lar {#viper-protocols}
VIPER'da modullarning o'zaro bog'liqligini (coupling) kamaytirish va testlashni osonlashtirish uchun har bir qismning aniq qoidalari (protokollari) yozib chiqiladi.
import UIKit
// 1. View Protocol (Presenter -> View)
protocol UserListViewProtocol: AnyObject {
func showLoading()
func hideLoading()
func displayUsers(_ users: [UserListViewModel])
func showError(_ message: String)
}
// 2. Presenter Protocol (View -> Presenter)
protocol UserListPresenterProtocol: AnyObject {
func viewDidLoad()
func didSelectUser(at index: Int)
}
// 3. Interactor Protocol (Presenter -> Interactor)
protocol UserListInteractorProtocol: AnyObject {
func fetchUsers()
}
// 4. Interactor Output Protocol (Interactor -> Presenter)
protocol UserListInteractorOutputProtocol: AnyObject {
func didFetchUsers(_ users: [UserEntity])
func didFailToFetch(with error: Error)
}
// 5. Router Protocol (Presenter -> Router)
protocol UserListRouterProtocol: AnyObject {
func navigateToUserDetails(with userId: String)
}
4. Module Builder va Dependency Injection {#module-builder}
Modul qismlari shu qadar ko'pki, ularni to'g'ri ketma-ketlikda yaratish va bir-biriga bog'lash alohida bir javobgarlikdir. Buning uchun Builder (yoki Configurator, Assembler) ishlatiladi.
import UIKit
final class UserListModuleBuilder {
static func build() -> UIViewController {
// 1. Barcha qismlarni yaratish
let view = UserListViewController()
let interactor = UserListInteractor()
let router = UserListRouter()
let presenter = UserListPresenter(view: view, interactor: interactor, router: router)
// 2. O'zaro bog'lash (Dependency Injection)
view.presenter = presenter
interactor.presenter = presenter // Interactor Output
router.viewController = view // Router navigatsiya uchun View'ni bilishi kerak
return view
}
}
5. Amaliy loyiha: User Profile VIPER Module {#amaliy-loyiha}
Quyida "Foydalanuvchi Profili" ekrani uchun to'liq ishlaydigan VIPER modulli kod keltirilgan. Bu yerda biz protokol va mantiqni qanday ajratishni ko'ramiz.
1. Entity (Model):
import Foundation
// Entity - Tarmoqdan keladigan sof ma'lumot
struct UserProfileEntity: Decodable {
let id: String
let firstName: String
let lastName: String
let age: Int
let bio: String
}
// ViewModel - View ga chiqarish uchun moslashtirilgan shakli
struct UserProfileViewModel {
let fullName: String
let infoText: String
}
2. Interactor:
import Foundation
protocol UserProfileInteractorProtocol: AnyObject {
func loadProfile()
}
protocol UserProfileInteractorOutputProtocol: AnyObject {
func profileLoaded(_ profile: UserProfileEntity)
func profileLoadFailed(_ error: String)
}
final class UserProfileInteractor: UserProfileInteractorProtocol {
weak var presenter: UserProfileInteractorOutputProtocol?
func loadProfile() {
// Asinxron API chaqiruvi simulyatsiyasi
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) { [weak self] in
let mockData = UserProfileEntity(
id: "123",
firstName: "Azimjon",
lastName: "Jalolov",
age: 25,
bio: "Senior iOS Developer at TechCompany."
)
DispatchQueue.main.async {
self?.presenter?.profileLoaded(mockData)
}
}
}
}
3. Router:
import UIKit
protocol UserProfileRouterProtocol: AnyObject {
func goBackToHome()
}
final class UserProfileRouter: UserProfileRouterProtocol {
weak var viewController: UIViewController?
func goBackToHome() {
viewController?.navigationController?.popViewController(animated: true)
}
}
4. Presenter:
import Foundation
protocol UserProfilePresenterProtocol: AnyObject {
func viewIsReady()
func closeButtonTapped()
}
final class UserProfilePresenter: UserProfilePresenterProtocol {
private weak var view: UserProfileViewProtocol?
private let interactor: UserProfileInteractorProtocol
private let router: UserProfileRouterProtocol
init(view: UserProfileViewProtocol,
interactor: UserProfileInteractorProtocol,
router: UserProfileRouterProtocol) {
self.view = view
self.interactor = interactor
self.router = router
}
func viewIsReady() {
view?.showLoading()
interactor.loadProfile()
}
func closeButtonTapped() {
router.goBackToHome()
}
}
// Interactor'dan kelgan natijani qabul qilamiz
extension UserProfilePresenter: UserProfileInteractorOutputProtocol {
func profileLoaded(_ profile: UserProfileEntity) {
view?.hideLoading()
// Entity -> ViewModel transformasiyasi
let viewModel = UserProfileViewModel(
fullName: "\(profile.firstName) \(profile.lastName)",
infoText: "Yosh: \(profile.age)\n\n\(profile.bio)"
)
view?.updateProfileInfo(with: viewModel)
}
func profileLoadFailed(_ error: String) {
view?.hideLoading()
view?.showError(error)
}
}
5. View (UIViewController):
import UIKit
protocol UserProfileViewProtocol: AnyObject {
func showLoading()
func hideLoading()
func updateProfileInfo(with viewModel: UserProfileViewModel)
func showError(_ message: String)
}
final class UserProfileViewController: UIViewController, UserProfileViewProtocol {
var presenter: UserProfilePresenterProtocol?
// UI Komponentlari
private let nameLabel = UILabel()
private let bioLabel = UILabel()
private let loadingIndicator = UIActivityIndicatorView(style: .large)
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
presenter?.viewIsReady()
}
private func setupUI() {
view.backgroundColor = .systemBackground
title = "Profile"
// UI sozlamalari (Native AutoLayout)
nameLabel.font = .boldSystemFont(ofSize: 24)
nameLabel.textAlignment = .center
bioLabel.font = .systemFont(ofSize: 16)
bioLabel.numberOfLines = 0
bioLabel.textAlignment = .center
bioLabel.textColor = .secondaryLabel
let stack = UIStackView(arrangedSubviews: [nameLabel, bioLabel])
stack.axis = .vertical
stack.spacing = 16
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
view.addSubview(loadingIndicator)
loadingIndicator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
stack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
stack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
loadingIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
loadingIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
// MARK: - Protocol Methods
func showLoading() {
loadingIndicator.startAnimating()
nameLabel.isHidden = true
bioLabel.isHidden = true
}
func hideLoading() {
loadingIndicator.stopAnimating()
nameLabel.isHidden = false
bioLabel.isHidden = false
}
func updateProfileInfo(with viewModel: UserProfileViewModel) {
nameLabel.text = viewModel.fullName
bioLabel.text = viewModel.infoText
}
func showError(_ message: String) {
let alert = UIAlertController(title: "Xato", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
}
6. VIPER va UITableView (ViewModel ishlashi) {#viper-tableview}
Agar ekraningizda UITableView bo'lsa, uni to'g'ri sozlash VIPER da biroz e'tibor talab qiladi.
Muhim:
Viewo'zining TableViewDataSourceichida to'g'ridan-to'g'riEntitybilan ishlamasligi shart!PresenterView ga massiv tarzidaViewModellar jo'natishi vaViewfaqat shularni chizishi kerak.
Qanday qilinadi?
- Interactor array of
[Entity]ni Presenterga yuboradi. - Presenter
[Entity]larni xarita (map) qilib[ViewModel]ga aylantiradi. - Presenter
view?.displayList(viewModels)ni chaqiradi. - View bu ViewModel'larni lokal xotirasiga olib,
.reloadData()qiladi va har bircellga ViewModel orqali ma'lumot uzatadi.
7. VIPER afzalliklari {#viper-afzalliklari}
- Testlashning osonligi: Interactor faqat biznes logikani, Presenter faqat ko'rinish mantiqini saqlagani uchun ularni Mock obyektlar yaratish orqali 100% qamrov bilan Unit Test qilish mumkin.
- Katta jamoalar uchun ideal: Bitta ekranni bir necha kishi yaratishi mumkin. Misol uchun: kimdir Interactor logikasini yozsa, boshqasi u bilan parallel ravishda View'ni UI'da terib yotishi mumkin, chunki ular protocol'lar orqali kelishib olgan.
- Single Responsibility (Yagona javobgarlik): Hech bir class juda kattalashib (Massive View Controller) ketmaydi. Har kim o'z ishini qiladi.
- Navigatsiya nazorati: Router mavjudligi tufayli ekranlar qanday ochilishi ViewController ichiga qotirib yozilmaydi, bu qayta ishlashni (reusability) oshiradi.
8. VIPER kamchiliklari {#viper-kamchiliklari}
- Haddan tashqari ko'p Boilerplate: Hatto eng oddiy (faqat bitta label bor) ekran uchun ham 6 ta protocol, 5 ta class yaratish kerak. Kichik loyihalarda bu mudhish vaqt yo'qotishiga olib keladi.
- Murakkablik: Yangi kelgan Junior dasturchilar uchun "Kim kimga ma'lumot beryapti?" degan oqimni (Data flow) tushunish juda qiyin bo'ladi.
- Fayllar ko'pligi: Loyiha tezda yuzlab fayllarga to'lib ketadi, yaxshi jild (folder) strukturasi bo'lmasa, arxitektura tartibsizlikka aylanadi.
9. Xcode Template va Code Generator'lar {#viper-generators}
VIPER ning asosiy dushmani — fayllarni qo'lda yaratishdagi zerikarlilikdir. Shuning uchun VIPER ishlatadigan jamoalar doim generatorlardan foydalanishadi:
- Xcode Templates: O'zingiz
File -> Newqilganingizda VIPER modulini tanlaydigan custom shablonlar o'rnatishingiz mumkin. Bu 5-6 ta faylni darhol siz yozgan ism bilan (masalan,Home) ochib beradi. - Generamba: Boshqa mashhur Ruby gem generatori. Konsol orqali
generamba gen HomeModule viperkabi buyruq berilsa, u hamma fayllarni to'g'ri proekt papsiga joylashtirib, hatto xcodeproj ga ham qo'shib beradi. - Zamonaviy muqobillar qatorida SwiftGen kabi tool'larning shaxsiy script'lari ham yoziladi.
10. VIPER vs MVVM vs MVC {#taqqoslash}
| Xususiyat | MVC | MVVM | VIPER |
|---|---|---|---|
| Qatlamlar soni | 3 (Model, View, Controller) | 3+ (Model, View, ViewModel, ba'zan Coordinator) | 5 (V, I, P, E, R) |
| O'rganish osonligi | Juda oson | O'rtacha (Reactive binding talab qiladi) | Qiyin |
| Testlash | Qiyin (Massive VC tufayli) | Oson | Juda oson (Izolyatsiya qilingan) |
| Mos keladigan loyiha | Kichik, oddiy | O'rta va Yirik | Keng ko'lamli (Enterprise), murakkab |
| Navigatsiya | VC ichida | Coordinator/Router ishlatilsa yaxshi | O'zining ichki Router'iga ega |
| Boilerplate code | Minimal | O'rtacha | Maksimal |
Xulosa {#xulosa}
VIPER arxitekturasi Clean Architecture g'oyalarini iOS loyihalariga olib kiruvchi eng kuchli qurollardan biridir. Uning aniq chegaralangan qatlamlari loyihani minglab ekranlarga yetganda ham barqaror va test qilishga tayyor holda ushlab turadi. Biroq, uni kichik loyihalar yoki tajribasiz jamoalar uchun qo'llash tavsiya etilmaydi, chunki murakkablik va yoziladigan kod (boilerplate) hajmi dastlabki mahsuldorlikni (productivity) keskin tushirib yuborishi mumkin. Zamonaviy iOS dasturlashda asosan MVVM-C (MVVM + Coordinator) unga asosiy va yengilroq raqobatchi sifatida ko'riladi, lekin yirik bank va fin-tech ilovalari ko'pincha VIPER ni tanlashadi.
Manbalar {#manbalar}
- Architecting iOS Apps with VIPER (MutualMobile)
- Clean Architecture by Robert C. Martin (Uncle Bob)
- iOS Architecture Patterns (Medium)
- Apple xcode qoliplari bo'yicha hujjatlar