# iOS Arxitektura 6: Clean Architecture — Uncle Bob Qoidalari, Qatlamlar va Dependency Rule

> **Holat:** 2026-yil oktabr holatiga ko'ra yozilgan. Zamonaviy iOS dasturlashda (Swift 6, iOS 17+) Clean Architecture prinsiplari loyihani uzoq muddat barqaror saqlash, masshtablash va testlashni osonlashtirish uchun eng kuchli yechimlardan biri hisoblanadi.

## Mundarija
1. [Clean Architecture nima?](#clean-architecture-nima)
2. [Dependency Rule (ENG MUHIM QOIDA!)](#dependency-rule)
3. [iOS'da Clean Architecture qatlamlari](#qatlamlar)
4. [Use Case (Interactor)](#use-case)
5. [Repository Pattern](#repository-pattern)
6. [Amaliy loyiha: E-Commerce (Clean Architecture)](#amaliy-loyiha)
7. [DTO vs Entity](#dto-vs-entity)
8. [Dependency Injection](#dependency-injection)
9. [Clean Architecture + MVVM kombinatsiyasi](#clean-mvvm)
10. [Unit Testing har bir qatlamda](#unit-testing)
11. [Xulosa](#xulosa)
12. [Manbalar](#manbalar)

---

## 1. Clean Architecture nima? {#clean-architecture-nima}

Clean Architecture — bu Robert C. Martin (Uncle Bob) tomonidan taklif qilingan dasturiy ta'minot arxitekturasi. Uning asosiy maqsadi tizimni qismlarga (qatlamlarga) ajratish orqali quyidagi xususiyatlarga erishishdir:

- **Framework-independent:** Arxitektura qandaydir tashqi kutubxona yoki framework'ga (masalan, UIKit, SwiftUI, CoreData) bog'lanib qolmaydi.
- **Testable:** Biznes mantiq (business rules) UI, Database yoki Web Server'siz oson test qilinishi mumkin.
- **UI-independent:** UI osongina o'zgarishi mumkin. Web UI Console UI bilan almashishi mumkin, bunda biznes mantiq o'zgarmaydi.
- **Database-independent:** Oracle yoki SQL Server o'rniga Mongo, CoreData yoki Realm ishlatilishi mumkin.
- **External Agency-independent:** Biznes mantiq tashqi dunyo haqida hech narsani bilmaydi.

**Konsentrik doiralar (Qatlamlar):**

![Clean Architecture Konsentrik Doiralar](/lessons/images/architecture/arch_06_clean_concentric_circles.svg)

---

## 2. Dependency Rule (ENG MUHIM QOIDA!) {#dependency-rule}

> **Muhim:** Clean Architecture'ning yadrosi bu — **Dependency Rule** (Bog'liqlik qoidasi).

Qoida shunday deydi: *Manba kodining bog'liqliklari faqat ICHKARIGA, yuqori darajadagi siyosatlar (policy) tomon yo'naltirilgan bo'lishi kerak.*

- **Entity (Ichki qatlam):** Hech qanday tashqi qatlamni bilmaydi. U faqat sof ma'lumotlar va biznes qoidalarini saqlaydi.
- **Use Case:** Entity'larni biladi, lekin UI yoki DB'ni bilmaydi.
- **Interface Adapters (ViewModel/Presenter):** Use Case'larni biladi, UI qanday chizilishini bilmaydi, lekin UI'ga qulay formatga o'tkazib beradi.
- **Frameworks (UIKit, CoreData, Alamofire):** Hammasini biladi va tashqi dunyo bilan aloqa qiladi.

Strelkalar faqat ICHKARIGA qaraydi:
`UI (View) -> ViewModel -> Use Case -> Entity`

![Clean Architecture Dependency Rule](/lessons/images/architecture/arch_06_clean_dependency_rule.svg)

---

## 3. iOS'da Clean Architecture qatlamlari {#qatlamlar}

iOS loyihalarida Clean Architecture odatda 3 ta asosiy qatlamga (Layer) bo'linadi. Kattaroq loyihalarda bu qatlamlar alohida Swift Package (SPM) modullari bo'lishi mumkin.

1. **Domain Layer (Yadro):** Eng ichki qatlam. O'ziga boshqa hech qaysi qatlamni import qilmaydi (hatto Foundation'dan tashqari boshqa framework'larni ham, masalan UIKit'ni mutlaqo kiritmaslik kerak).
   - *Entity'lar* (Biznes modellar)
   - *Use Case Protocol'lari* (Biznes logikasi)
   - *Repository Protocol'lari* (Data saqlash qoidalari, lekin implementation emas!)

2. **Data Layer:** Tashqi ma'lumotlar manbai bilan ishlash.
   - *Repository Implementation'lar*
   - *API Service'lar* (Network requests)
   - *Local Storage* (CoreData, UserDefaults)
   - *DTO (Data Transfer Objects)* va ularni Entity'ga o'giruvchi Mapper'lar.

3. **Presentation Layer:** Foydalanuvchi interfeysi.
   - *Views* (SwiftUI Views, UIViewController)
   - *ViewModels / Presenters* (Holatni boshqarish)
   - Bu qatlam Domain layer'dagi Use Case'larni chaqiradi.

---

## 4. Use Case (Interactor) {#use-case}

Use Case — bu dasturdagi bitta aniq harakat yoki biznes operatsiya. Masalan, foydalanuvchini ro'yxatdan o'tkazish, mahsulotlarni yuklash.

Nima uchun kerak? U ViewModel'ni juda semirib ketishdan asraydi va qandaydir amalni bajarish ketma-ketligini (business flow) o'zida saqlaydi.

```swift
import Foundation

// Domain Layer
protocol FetchUsersUseCaseProtocol {
    func execute() async throws -> [User]
}

// Interactor Implementation
final class FetchUsersUseCase: FetchUsersUseCaseProtocol {
    private let userRepository: UserRepositoryProtocol
    
    // DI orqali Repository qabul qilinadi
    init(userRepository: UserRepositoryProtocol) {
        self.userRepository = userRepository
    }
    
    func execute() async throws -> [User] {
        // Bu yerda biznes logika bo'lishi mumkin. Masalan, faqat aktiv userlarni filtrlash.
        let users = try await userRepository.fetchUsers()
        return users.filter { $0.isActive }
    }
}
```

---

## 5. Repository Pattern {#repository-pattern}

Repository data layer va domain layer o'rtasidagi ko'prik vazifasini bajaradi. Domain layer "Menga ma'lumot kerak" deydi (Protocol orqali), Data layer esa "Men buni API yoki DB dan olib kelib beraman" deydi (Implementation).

```swift
// 1. Domain Layer'dagi Protocol
protocol UserRepositoryProtocol {
    func fetchUsers() async throws -> [User]
}

// 2. Data Layer'dagi Implementation (Real API)
final class UserRepository: UserRepositoryProtocol {
    let apiService: APIService
    
    init(apiService: APIService) {
        self.apiService = apiService
    }
    
    func fetchUsers() async throws -> [User] {
        let userDTOs = try await apiService.request(endpoint: "/users")
        return userDTOs.map { $0.toDomain() } // DTO'ni Entity'ga o'giramiz
    }
}

// 3. Test'da ishlatish uchun Mock Repository
final class MockUserRepository: UserRepositoryProtocol {
    var shouldReturnError = false
    
    func fetchUsers() async throws -> [User] {
        if shouldReturnError {
            throw URLError(.badServerResponse)
        }
        return [User(id: "1", name: "Eshmat", isActive: true)]
    }
}
```

---

## 6. Amaliy loyiha: E-Commerce (Clean Architecture) {#amaliy-loyiha}

Endi hamma qismlarni birlashtirib, E-Commerce ilovasidagi "Mahsulotlar ro'yxatini ko'rsatish" ekranini yaratamiz.

```swift
import UIKit
// SnapKit o'rniga misol uchun oddiy Constraints yozamiz, lekin tasavvur qiling SnapKit ishlatilyapti.

// ==========================================
// MARK: - 1. DOMAIN LAYER
// ==========================================
struct Product {
    let id: String
    let title: String
    let price: Double
}

protocol ProductRepositoryProtocol {
    func fetchProducts() async throws -> [Product]
}

protocol FetchProductsUseCaseProtocol {
    func execute() async throws -> [Product]
}

final class FetchProductsUseCase: FetchProductsUseCaseProtocol {
    private let repository: ProductRepositoryProtocol
    
    init(repository: ProductRepositoryProtocol) {
        self.repository = repository
    }
    
    func execute() async throws -> [Product] {
        return try await repository.fetchProducts()
    }
}

// ==========================================
// MARK: - 2. DATA LAYER
// ==========================================
struct ProductDTO: Codable {
    let productId: String
    let name: String
    let priceString: String // API dan string kelyapti
    
    func toDomain() -> Product {
        return Product(
            id: productId,
            title: name,
            price: Double(priceString) ?? 0.0
        )
    }
}

final class ProductRepository: ProductRepositoryProtocol {
    func fetchProducts() async throws -> [Product] {
        // Aslida URLSession orqali tarmoqqa so'rov yuboriladi
        try await Task.sleep(nanoseconds: 1_000_000_000)
        let mockDTOs = [
            ProductDTO(productId: "1", name: "iPhone 16", priceString: "999.0"),
            ProductDTO(productId: "2", name: "MacBook Pro", priceString: "1999.0")
        ]
        return mockDTOs.map { $0.toDomain() }
    }
}

// ==========================================
// MARK: - 3. PRESENTATION LAYER (MVVM)
// ==========================================
@MainActor
final class ProductListViewModel {
    private let fetchProductsUseCase: FetchProductsUseCaseProtocol
    
    // Outputs
    var onStateChanged: ((ViewState) -> Void)?
    private(set) var products: [Product] = []
    
    enum ViewState {
        case loading
        case success
        case error(String)
    }
    
    init(fetchProductsUseCase: FetchProductsUseCaseProtocol) {
        self.fetchProductsUseCase = fetchProductsUseCase
    }
    
    func loadProducts() {
        onStateChanged?(.loading)
        
        Task {
            do {
                self.products = try await fetchProductsUseCase.execute()
                onStateChanged?(.success)
            } catch {
                onStateChanged?(.error(error.localizedDescription))
            }
        }
    }
}

// ==========================================
// MARK: - 4. UI (VIEW CONTROLLER)
// ==========================================
final class ProductListViewController: UIViewController {
    private let viewModel: ProductListViewModel
    
    private let tableView = UITableView()
    private let activityIndicator = UIActivityIndicatorView(style: .large)
    
    init(viewModel: ProductListViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        bindViewModel()
        viewModel.loadProducts()
    }
    
    private func setupUI() {
        title = "Products"
        view.backgroundColor = .systemBackground
        
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        
        view.addSubview(tableView)
        view.addSubview(activityIndicator)
        
        // SnapKit style (using Autolayout directly here for out-of-the-box compiling)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            
            activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
    private func bindViewModel() {
        viewModel.onStateChanged = { [weak self] state in
            guard let self = self else { return }
            switch state {
            case .loading:
                self.activityIndicator.startAnimating()
                self.tableView.isHidden = true
            case .success:
                self.activityIndicator.stopAnimating()
                self.tableView.isHidden = false
                self.tableView.reloadData()
            case .error(let message):
                self.activityIndicator.stopAnimating()
                print("Error: \(message)")
            }
        }
    }
}

extension ProductListViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.products.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        let product = viewModel.products[indexPath.row]
        cell.textLabel?.text = "\(product.title) - $\(product.price)"
        return cell
    }
}

// ==========================================
// MARK: - 5. DI CONTAINER (COMPOSITION ROOT)
// ==========================================
final class DIContainer {
    func makeProductListViewController() -> UIViewController {
        let repository = ProductRepository()
        let useCase = FetchProductsUseCase(repository: repository)
        let viewModel = ProductListViewModel(fetchProductsUseCase: useCase)
        let viewController = ProductListViewController(viewModel: viewModel)
        return viewController
    }
}
```

---

## 7. DTO vs Entity {#dto-vs-entity}

Nima uchun API'dan kelgan `Codable` modelni (DTO) to'g'ridan-to'g'ri loyiha bo'ylab ishlatmaymiz? Nima uchun Entity va DTO alohida?

- **DTO (Data Transfer Object):** API'dan keluvchi raw (xom) ma'lumot. API'ning formati bizga mos tushmasligi mumkin (masalan `price` string ko'rinishida kelishi, sanalar har xil formatda bo'lishi).
- **Entity:** Dasturning ichki, "toza" modeli. Barcha mantiq unga asoslanadi.
- **Mapper:** DTO'ni Entity'ga o'giruvchi funksiya.

> **Senior eslatmasi:** Tasavvur qiling backend komandasi API response'ni o'zgartirdi (`user_name` o'rniga `first_name` qildi). Agar siz hamma joyda DTO ishlatsangiz, barcha ekranlar va kodlarni o'zgartirib chiqishingiz kerak. Clean Architecture'da esa faqat DTO va Mapper o'zgaradi, qolgan kod buzilmaydi.

---

## 8. Dependency Injection {#dependency-injection}

Clean Architecture amalga oshishi uchun obyektlar o'zlariga kerakli narsalarni o'zi yaratmasligi, balki tashqaridan (Protocol orqali) qabul qilishi kerak. Bu **Dependency Injection (DI)** deyiladi.

1. **Constructor Injection (Eng yaxshi amaliyot):** Barcha bog'liqliklar `init` orqali beriladi (Yuqoridagi kodlarda shu ishlatilgan).
2. **Property Injection:** Bog'liqliklar propertiyalar orqali o'rnatiladi. (Odatda Storyboard ishlatilganda kerak bo'ladi).

Loyiha kattalashganda `Swinject` yoki `Factory` kabi DI framework'lardan foydalanish tavsiya etiladi.

---

## 9. Clean Architecture + MVVM kombinatsiyasi {#clean-mvvm}

iOS'da odatda **Clean Architecture** bilan birgalikda Presentation layer'da **MVVM (Model-View-ViewModel)** ishlatiladi.

**Oqim (Data Flow):**
1. User tugmani bosadi (View).
2. View ViewModel'ga `viewModel.loadData()` buyrug'ini beradi.
3. ViewModel Use Case'ga `useCase.execute()` ni chaqiradi.
4. Use Case Repository'dan `repository.fetch()` so'raydi.
5. Repository Data Source (API/DB) dan DTO olib, Entity'ga aylantirib qaytaradi.
6. Use Case Entity'ni ViewModel'ga qaytaradi.
7. ViewModel ma'lumotni tayyorlab, View'ga signal beradi (`onStateChanged`).
8. View yangilangan ma'lumotni chizadi.

---

## 10. Unit Testing har bir qatlamda {#unit-testing}

Clean Architecture ning eng katta yutug'i — Testable (test qilinishi osonligidadir).

```swift
import XCTest

// Use Case ni test qilish
final class FetchProductsUseCaseTests: XCTestCase {
    
    // Mock qilingan Repository yaratamiz
    class MockProductRepository: ProductRepositoryProtocol {
        var resultToReturn: [Product] = []
        var shouldThrowError = false
        
        func fetchProducts() async throws -> [Product] {
            if shouldThrowError {
                throw URLError(.notConnectedToInternet)
            }
            return resultToReturn
        }
    }
    
    func testFetchProducts_Success() async throws {
        // Arrange (Tayyorgarlik)
        let mockRepository = MockProductRepository()
        mockRepository.resultToReturn = [Product(id: "1", title: "Test", price: 10)]
        let sut = FetchProductsUseCase(repository: mockRepository) // sut = System Under Test
        
        // Act (Harakat)
        let products = try await sut.execute()
        
        // Assert (Tekshirish)
        XCTAssertEqual(products.count, 1)
        XCTAssertEqual(products.first?.title, "Test")
    }
}
```

Har bir qatlam o'zining "Mock" obyektlari yordamida izolyatsiya qilingan holda test qilinadi. ViewModel uchun Mock Use Case, Use Case uchun Mock Repository ishlatiladi.

---

## Xulosa {#xulosa}

- **Clean Architecture** sizga masshtablash (scalability) va o'zgarishlarga chidamli (maintainable) kod yozish imkonini beradi.
- **Dependency Rule** qoidasiga ko'ra ichki qatlam tashqi qatlamni mutlaqo bilmasligi shart.
- Kichik loyihalar uchun Clean Architecture ortiqcha bo'lishi mumkin (over-engineering). Lekin o'rta va katta korporativ loyihalar uchun u ajralmas qismdir.
- Framework'larga (UIKit, SwiftUI, CoreData) tobe bo'lib qolishdan saqlaning.

## Manbalar {#manbalar}

- [The Clean Architecture by Uncle Bob (Robert C. Martin)](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
- [Clean Architecture for iOS Development (RayWenderlich / Kodeco)](https://www.kodeco.com/)
- [Advanced iOS App Architecture](https://www.kodeco.com/books/advanced-ios-app-architecture)

