Azimjon Abdurasulov

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

  1. VIPER nima?
  2. VIPER ma'lumot oqimi (Data Flow)
  3. Har bir qism uchun protocol'lar
  4. Module Builder va Dependency Injection
  5. Amaliy loyiha: User Profile VIPER Module
  6. VIPER va UITableView (ViewModel ishlashi)
  7. VIPER afzalliklari
  8. VIPER kamchiliklari
  9. Xcode Template va Code Generator'lar
  10. VIPER vs MVVM vs MVC
  11. Xulosa
  12. 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). UIViewController yoki 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):

  1. View ekranga chiqdi (viewDidLoad). U Presenter'ga aytadi: presenter.viewDidLoad().
  2. Presenter Interactor'ga buyruq beradi: interactor.fetchUsers().
  3. Interactor tarmoqdan (Service) ma'lumotlarni (Entity) oladi va Presenter'ga qaytaradi (Output protocol orqali).
  4. Presenter olingan Entity'larni View uchun qulay bo'lgan ViewModel'larga aylantiradi va View'ga yuboradi: view.showUsers(viewModels).
  5. 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: View o'zining TableView DataSource ichida to'g'ridan-to'g'ri Entity bilan ishlamasligi shart! Presenter View ga massiv tarzida ViewModel lar jo'natishi va View faqat shularni chizishi kerak.

Qanday qilinadi?

  1. Interactor array of [Entity] ni Presenterga yuboradi.
  2. Presenter [Entity] larni xarita (map) qilib [ViewModel] ga aylantiradi.
  3. Presenter view?.displayList(viewModels) ni chaqiradi.
  4. View bu ViewModel'larni lokal xotirasiga olib, .reloadData() qiladi va har bir cell ga ViewModel orqali ma'lumot uzatadi.

7. VIPER afzalliklari {#viper-afzalliklari}

  1. 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.
  2. 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.
  3. Single Responsibility (Yagona javobgarlik): Hech bir class juda kattalashib (Massive View Controller) ketmaydi. Har kim o'z ishini qiladi.
  4. Navigatsiya nazorati: Router mavjudligi tufayli ekranlar qanday ochilishi ViewController ichiga qotirib yozilmaydi, bu qayta ishlashni (reusability) oshiradi.

8. VIPER kamchiliklari {#viper-kamchiliklari}

  1. 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.
  2. Murakkablik: Yangi kelgan Junior dasturchilar uchun "Kim kimga ma'lumot beryapti?" degan oqimni (Data flow) tushunish juda qiyin bo'ladi.
  3. 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 -> New qilganingizda 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 viper kabi 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}

XususiyatMVCMVVMVIPER
Qatlamlar soni3 (Model, View, Controller)3+ (Model, View, ViewModel, ba'zan Coordinator)5 (V, I, P, E, R)
O'rganish osonligiJuda osonO'rtacha (Reactive binding talab qiladi)Qiyin
TestlashQiyin (Massive VC tufayli)OsonJuda oson (Izolyatsiya qilingan)
Mos keladigan loyihaKichik, oddiyO'rta va YirikKeng ko'lamli (Enterprise), murakkab
NavigatsiyaVC ichidaCoordinator/Router ishlatilsa yaxshiO'zining ichki Router'iga ega
Boilerplate codeMinimalO'rtachaMaksimal

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}

Markdown manba

iOS Lessons