01. MVC — Apple'ning Asl Pattern'i va Massive View Controller
Arxitektura
iOS Arxitektura 1: MVC — Apple'ning Asl Pattern'i, Massive View Controller Muammosi va To'g'ri MVC
Holat: Ushbu dars 2026-yil oktabr oyi holatiga ko'ra iOS arxitekturalari evolyutsiyasining boshlang'ich nuqtasi bo'lgan MVC (Model-View-Controller) naqshini, uning muammolarini va to'g'ri qo'llanilishini o'z ichiga oladi. Maqoladagi kodlar Swift 6 va iOS 17+ texnologiyalariga moslashtirilgan.
Mundarija
- MVC nima?
- Nega MVC iOS'da standart?
- "Massive View Controller" muammosi
- To'g'ri MVC qanday bo'lishi kerak
- Amaliy loyiha: To'do List (To'g'ri MVC)
- MVC qachon to'g'ri tanlov?
- MVC kamchiliklari
1. MVC nima? {#mvc-nima}
MVC (Model-View-Controller) — bu dasturiy ta'minotni uchta asosiy mantiqiy qismga ajratuvchi arxitektura pattern'i. iOS dasturlashda u uzoq yillar davomida Apple tomonidan rasman tavsiya qilingan asosiy yondashuv bo'lib kelgan.
Ushbu uch komponentning har biri o'ziga xos vazifaga ega:
- Model: Ilovaning ma'lumotlari va biznes logikasini saqlaydi. Bunga tarmoqdan kelgan ma'lumotlar (
struct/class,Codable), CoreData modellari, hisob-kitob qoidalari va holat kiradi. Model hech qachon View yoki Controller haqida bilmaydi. - View: Foydalanuvchi ko'radigan barcha vizual interfeys elementlari (masalan,
UIView,UILabel,UIButton). View ma'lumotni qanday qilib ekranga chiqarishni biladi, lekin o'sha ma'lumot qayerdan kelishini va nima ekanligini bilmaydi. - Controller: Model va View orasidagi bog'lovchi vositachi (
UIViewController). View'dan foydalanuvchi harakatlarini (masalan, tugma bosilishi) qabul qiladi, Model'ni o'zgartiradi. So'ngra Model'dan olingan yangi ma'lumotlar bilan View'ni yangilaydi.
Apple'ning rasmiy MVC ma'lumotlar oqimi:
Apple MVC Ma'lumotlar Oqimi
Senior eslatmasi: Apple'ning an'anaviy MVC'si sof (Classic) MVC'dan farq qiladi. Classic MVC'da View Model'ni kuzatishi (observe) mumkin edi. iOS MVC'sida esa View va Model bir-birini umuman bilmasligi kerak. Hamma aloqa Controller orqali o'tishi shart.
2. Nega MVC iOS'da standart? {#nega-mvc}
Apple boshidanoq MVC'ni iOS SDK'sining asosi qilib olgan. Bunga bir nechta sabablar bor:
- UIKit va Controller birlashuvi:
UIViewControlleraynan MVC'dagi Controller vazifasini bajarish uchun yaratilgan. U View'ning hayot siklini (viewDidLoad,viewWillAppear) boshqaradi va ayni paytda biznes logikani boshqarishga imkon beradi. - Tez va oson (O'rganish uchun qulay): Kichik loyihalar yoki oddiy ekranlar uchun arxitektura qo'shimcha qatlamlarsiz osongina tushunarli. Interface Builder (Storyboard/XIB) bilan Controller orqali ulanishlar (@IBOutlet, @IBAction) juda to'g'ridan-to'g'ri.
- Standart Xcode Shabloni: Yangi iOS loyiha ochganingizda Xcode avtomatik tarzda
ViewController.swiftfaylini yaratib beradi va sizni MVC ishlatishga undaydi.
3. "Massive View Controller" muammosi {#massive-vc}
Nazariy jihatdan MVC ajoyib eshitiladi, biroq iOS amaliyotida ko'pincha "Massive View Controller" (Ulkan View Controller) deb nomlangan antipatternga aylanib ketadi.
Muammo shundaki, Controller (UIViewController) hamma narsaning markazida bo'lgani uchun, yangi boshlagan yoki shoshayotgan dasturchilar barcha kodni uning ichiga yozib yuborishadi: tarmoq so'rovlari (Networking), ma'lumotlarni o'zgartirish (Formatting), navigatsiya (Routing), animatsiyalar va hatto ma'lumotlar bazasi (CoreData) bilan ishlash. Natijada UIViewController 1000+ qatorli "maxluq"qa aylanadi.
Yomon MVC (Massive View Controller) kodi misoli:
import UIKit
// YOMON MISOL: Hamma narsa bitta Controller ichida
class BadTodoListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var tableView: UITableView!
var todos: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
fetchTodosFromNetwork()
}
// NETWORKING CONTROLLER ICHIDA!
func fetchTodosFromNetwork() {
let url = URL(string: "https://api.example.com/todos")!
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]
let parsedTodos = json?.compactMap { $0["title"] as? String } ?? []
DispatchQueue.main.async {
self?.todos = parsedTodos
self?.tableView.reloadData()
}
} catch {
print("Error parsing JSON")
}
}
}.resume()
}
// FORMATTING CONTROLLER ICHIDA!
func formatTitle(_ title: String) -> String {
return title.uppercased() + " 📌"
}
// UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
// Formatting to'g'ridan to'g'ri View Controllerda
cell.textLabel?.text = formatTitle(todos[indexPath.row])
return cell
}
// NAVIGATION CONTROLLER ICHIDA!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let detailVC = DetailViewController()
detailVC.todoTitle = todos[indexPath.row]
self.navigationController?.pushViewController(detailVC, animated: true)
}
}
Massive View Controller oqibatlari:
- Test qilib bo'lmaslik (Untestable): Networking, View lifecycle va ma'lumotlar bitta joyda bog'lanib qolgani uchun,
formatTitleyoki biznes logikani alohida unit test qilish juda qiyin. - Takroriy kodlar (DRY buzilishi): Agar boshqa ekranda ham API chaqirish kerak bo'lsa, xuddi shu kod u yerda ham yoziladi.
- Katta jamoalarda konfiliktlar: Hamma bitta faylda ishlashiga to'g'ri keladi, bu esa "Merge Conflict"larga olib keladi.
4. To'g'ri MVC qanday bo'lishi kerak {#togri-mvc}
Aslida MVC yomon arxitektura emas, uni to'g'ri qo'llash kerak. To'g'ri MVC ning oltin qoidasi: "Fat Model, Skinny Controller" (Semiz Model, Oriq Controller). Ya'ni ko'p mantiq va ish Model qatlamida bo'lishi, Controller esa faqatgina ularni View bilan bog'lash vazifasinigina bajarishi kerak.
Massive View Controller vs To'g'ri MVC
To'g'ri MVC qilish uchun qoidalar:
- Tarmoq so'rovlarini Service/Manager sinflariga o'tkazish: Networking Controller ichida bo'lmasligi kerak.
- Ma'lumotlar modelini kuchaytirish: Formatting, biznes logika modelning o'zida bo'lishi lozim.
- Viewlarni mustaqil qilish: Hamma UI konfiguratsiyalari maxsus
UIViewyokiUITableViewCellsinflarida bo'lishi kerak. - Navigatsiyani ajratish: (Agar xohlasangiz Router yoki Delegate pattern orqali ajratish mumkin).
5. Amaliy loyiha: To'do List (To'g'ri MVC) {#amaliy-loyiha}
Endi keling, Swift 6 va iOS 17'ning zamonaviy yondashuvlaridan (async/await, @MainActor) foydalanib, MVC'ni to'g'ri implementatsiya qilamiz.
Ushbu amaliyotda mas'uliyatlar yuqoridagi sxema asosida 4 ta toza qatlamga (TodoCell, TodoListViewController, TodoService va Todo) ajratiladi.
1. Model (Ma'lumot va Biznes logika)
Model o'z ma'lumotlarini qanday formatlashni o'zi bilishi kerak. Bu yerni test qilish juda oson.
import Foundation
struct Todo: Codable, Equatable {
let id: UUID
let title: String
let isCompleted: Bool
// Biznes logika va formatting Model ichida!
var formattedTitleForDisplay: String {
return isCompleted ? "✅ \(title)" : "📌 \(title)"
}
}
2. Service (Networking / Data Management)
Controller'ni tozalash uchun tarmoq logikasini xizmatga ajratamiz.
import Foundation
protocol TodoServiceProtocol: Sendable {
func fetchTodos() async throws -> [Todo]
}
final class TodoService: TodoServiceProtocol {
func fetchTodos() async throws -> [Todo] {
// Simulyatsiya qilingan tarmoq chaqiruvi (Real API bilan almashtirilishi mumkin)
try await Task.sleep(for: .seconds(1))
return [
Todo(id: UUID(), title: "Swift 6 o'rganish", isCompleted: true),
Todo(id: UUID(), title: "To'g'ri MVC yozish", isCompleted: false),
Todo(id: UUID(), title: "Unit Testlarni yozish", isCompleted: false)
]
}
}
3. View (Faqat UI)
Hujayra qanday ko'rinishda bo'lishini o'zi biladi. Modelni o'zlashtiradi, lekin qanday qilib olinganini bilmaydi.
import UIKit
@MainActor
final class TodoCell: UITableViewCell {
static let reuseIdentifier = "TodoCell"
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
private func setupUI() {
textLabel?.font = .systemFont(ofSize: 16, weight: .medium)
}
// Hujayra o'zini o'zi model orqali konfiguratsiya qiladi
func configure(with model: Todo) {
textLabel?.text = model.formattedTitleForDisplay
textLabel?.textColor = model.isCompleted ? .systemGreen : .label
}
}
4. Controller (Vositachi)
Endi Controller juda ixcham ("Skinny") va tushunarli. U faqat Service'ni chaqiradi va View'ni (TableView) yangilaydi.
import UIKit
@MainActor
final class TodoListViewController: UIViewController {
// UI Elements
private let tableView: UITableView = {
let table = UITableView()
table.register(TodoCell.self, forCellReuseIdentifier: TodoCell.reuseIdentifier)
table.translatesAutoresizingMaskIntoConstraints = false
return table
}()
private let activityIndicator = UIActivityIndicatorView(style: .medium)
// Dependencies (Qaramliklar)
private let todoService: TodoServiceProtocol
private var todos: [Todo] = []
// Dependency Injection yordamida Service'ni kiritish
init(todoService: TodoServiceProtocol = TodoService()) {
self.todoService = todoService
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
// View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
loadTodos()
}
private func setupUI() {
title = "MVC Todos"
view.backgroundColor = .systemBackground
view.addSubview(tableView)
tableView.dataSource = self
tableView.delegate = self
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
// Action
private func loadTodos() {
Task {
do {
// Service chaqiruvi
todos = try await todoService.fetchTodos()
tableView.reloadData()
} catch {
print("Xatolik: \(error.localizedDescription)")
}
}
}
}
// MARK: - UITableViewDataSource & Delegate
extension TodoListViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: TodoCell.reuseIdentifier, for: indexPath) as? TodoCell else {
return UITableViewCell()
}
let todo = todos[indexPath.row]
cell.configure(with: todo) // View Modelni qabul qiladi
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let selectedTodo = todos[indexPath.row]
print("Tanlandi: \(selectedTodo.title)")
// Navigatsiya logika shu yerda yoki Router orqali bo'lishi mumkin
}
}
5. Unit Testing Misoli
Arxitekturani to'g'ri bo'lgani sababli, endi biz Model va Service'larni UI (Controller) ni jalb qilmasdan osonlik bilan Unit Test qila olamiz.
import XCTest
@testable import YourAppModule
final class TodoModelTests: XCTestCase {
func testTodoFormattingCompleted() {
let todo = Todo(id: UUID(), title: "Test", isCompleted: true)
XCTAssertEqual(todo.formattedTitleForDisplay, "✅ Test")
}
func testTodoFormattingIncomplete() {
let todo = Todo(id: UUID(), title: "Test", isCompleted: false)
XCTAssertEqual(todo.formattedTitleForDisplay, "📌 Test")
}
}
// Service ni Mock qilib test qilish oson
final class MockTodoService: TodoServiceProtocol {
func fetchTodos() async throws -> [Todo] {
return [Todo(id: UUID(), title: "Mock", isCompleted: true)]
}
}
6. MVC qachon to'g'ri tanlov? {#qachon-mvc}
Garchi MVVM, VIPER kabi murakkab patternlar urfda bo'lsa ham, MVC ning o'z joyi bor:
| Qachon ishlatish kerak? | Nega? |
|---|---|
| Kichik loyihalar (1-3 ta ekran) | Keraksiz ko'p fayllar va qo'shimcha qatlamlarni (over-engineering) oldini oladi. |
| Tezkor prototiplar / Hackathon'lar | Vaqt kam bo'lganida natijani tezroq chiqarish imkonini beradi. |
| Oddiy CRUD ilovalari | Agar ilova faqat ro'yxatni ko'rsatish va oddiy API call qilishdan iborat bo'lsa, MVC ideal. |
| Yangi o'rganuvchilar uchun | View, Controller va Model orasidagi sodda aloqani o'rganish uchun fundamental qadam. |
Muhim: SwiftUI kelishi bilan "Massive View Controller" muammosi o'z-o'zidan yo'qoldi (chunki Controller'ning o'zi yo'q). Lekin UIKit loyihalarni qo'llab-quvvatlash uchun MVC qoidalari hali ham dolzarb.
7. MVC kamchiliklari {#kamchiliklar}
Hatto eng to'g'ri va "Skinny" MVC qilsangiz ham ba'zi kamchiliklar saqlanib qoladi:
- Tight Coupling (Qattiq bog'liqlik): View va Controller baribir bir-biriga qattiq bog'langan bo'ladi (
@IBOutletva UI elementlar orqali). View Controller'ni alohida UI'dan mustaqil tarzda test qilib bo'lmaydi. - Controller hali ham ko'p ish qiladi: Service qatlamlarini ajratsangiz ham, ma'lumotni View'ga olib o'tish (Viewlarni yangilash kodi) va foydalanuvchi harakatlarini eshitish baribir Controller'da qoladi.
- Katta va o'rta loyihalar uchun mo'ljallanmagan: Ko'plab holatlar (state) va biznes qoidalari ko'paygan sari, Controller'dagi kod yana kattalasha boshlaydi va keyingi bosqich — MVVM (Model-View-ViewModel) ga ehtiyoj tug'diradi.
Xulosa {#xulosa}
MVC — Apple ekotizimida ko'p yillar davomida podshoh bo'lib kelgan. U oddiy va ishonchli. Biroq u noto'g'ri qo'llanilganda, "Massive View Controller" balosiga olib kelishi aniq. To'g'ri MVC tuzilishi uchun "Skinny Controller" qoidasiga amal qiling, tarmoq chaqiruvlari, ma'lumotlar ustida amallar va qolgan logikalarni Controller'dan chiqarib, Model yoki alohida Servis qatlamiga o'tkazing. MVC'ni to'g'ri tushunish, kelajakda MVVM va VIPER kabi arxitekturalarni o'zlashtirish uchun mustahkam poydevor hisoblanadi.
Manbalar {#manbalar}
- Model-View-Controller in Apple Documentation
- Swift 6 Concurrency & MainActor - Swift.org
- "iOS Architecture Patterns" - Boshqa zamonaviy yondashuvlarni o'rganish bo'yicha qo'llanmalar.