# Tuist 06: Modular arxitektura (TMA) — µFeature'lar, Interface/Implementation, Testing va Example ilovalar

> **Holat:** 2026-yil oktabr. Bu dars Tuist tavsiya qiladigan **The Modular Architecture (TMA)** ni noldan quradi: µFeature anatomiyasi, Interface/Implementation ajratish, Testing va Example target'lar, dependency injection va "Interface o'zgarmasa, dependent qayta qurilmaydi" degan asosiy da'voni real o'lchov bilan tekshiradi. Kod misollari Tuist 4.211.1 va Xcode 27.1 bilan Apple M4 Pro'da tekshirilgan.

> **Bu darsdan keyin siz:** bitta feature'ni 5 ta target (Interface, Source, Testing, Tests, Example) ga ajratib, `ProjectDescriptionHelpers` factory bilan avtomatlashtira olasiz va modullar orasidagi bog'lanishni Interface orqali boshqarib, build vaqtini kamaytirasiz.

- Monolit (bitta target) loyihaning aniq og'riqlarini va modullarga bo'lish ularni qanday yechishini tushunasiz.
- µFeature / TMA ning 5 target anatomiyasini va har birining vazifasini bilasiz.
- "Source'ga emas, Interface'ga bog'lan" qoidasini va u build'ni nega tezlatishini mexanizm darajasida tushunasiz.
- `Module.feature(...)` factory yozib, 2 ta feature + 1 core modulni bitta `Project.swift` dan generatsiya qilasiz.
- Dependency injection'ni composition root'da yig'asiz; statik/dinamik product type'ni `Environment` orqali almashtirasiz.
- `tuist inspect dependencies` bilan Interface chegarasini majburlaysiz va implicit bog'liqliklarni ushlaysiz.

## Mundarija

1. [Monolit azobi: nega modullarga bo'lamiz?](#nega-modul)
2. [TMA nima va "µFeature" qayerdan keldi?](#tma-nima)
3. [Modul anatomiyasi: 5 ta target](#anatomiya)
4. [Asosiy qoida: Interface'ga bog'lan, Source'ga emas](#qoida)
5. [Loyihani quramiz: ProjectDescriptionHelpers factory](#factory)
6. [Modullar kodi: Interface, Source, Testing, Tests, Example](#modul-kodi)
7. [Dependency injection: implementatsiyani bir joyda yig'ish](#di)
8. [Statik va dinamik: product type va dynamic configuration](#product-type)
9. [O'lchov: Interface o'zgarmasa, dependent qayta qurilmaydi](#olchov)
10. [Chegarani majburlash: tuist inspect dependencies](#inspect)
11. [Example app bilan tez iteratsiya](#example)
12. [Katta miqyosda: Monzo, Toss, monday.com](#miqyos)
13. [Kamchiliklar va qachon TMA kerak emas](#kamchilik)
14. [Amaliy mashqlar](#mashqlar)
15. [O'zingizni tekshiring](#checklist)
16. [Xulosa](#xulosa)
17. [Manbalar](#manbalar)

---

## 1. Monolit azobi: nega modullarga bo'lamiz? {#nega-modul}

Avval muammoni his qilaylik. Tasavvur qiling: butun ilovangiz — barcha view, view model, service, model — bitta Xcode target'da. Loyiha 10 ming satrgacha hammasi joyida. Lekin kod 100 mingga, keyin 300 mingga o'sadi. Endi:

- Bitta kichik o'zgarish kiritasiz — Xcode **butun target'ni** qayta kompilyatsiya qiladi, chunki hamma narsa bitta modulda. Bir qatorlik tuzatish uchun 45 sekund kutasiz.
- SwiftUI Preview'lar sekinlashadi, ba'zan umuman ishlamaydi — Preview butun target'ni build qilishi kerak.
- Kod o'zaro chigallashadi: har kim hamma narsani `import`siz ko'radi, implicit bog'liqlik ("spaghetti") paydo bo'ladi, holat (state) oldindan aytib bo'lmas bo'lib qoladi.
- Yangi dasturchi loyihaga kirsa, qayerdan boshlashni bilmaydi — chegara yo'q.

Bu faraz emas. **Asana** jamoasi aynan shu holatga tushgan: ~10 muhandis ~300 ming satr Swift kodni deyarli **bitta target**da yuritardi. Ularning muhandislik blogiga ko'ra (2023-yil 21-fevral), o'zgarish kiritib simulyatorda ko'rishgacha ketadigan vaqt odatda **~45 sekund** edi (eski Intel Mac'larda ~90 s). Jamoa loyihani Tuist bilan µFeature'larga bo'lgandan keyin, Scalyr'da yozib olingan **median build vaqti 15 sekund**ga tushdi, Inbox modulidagi bir qatorlik o'zgarish esa 30–50 s dan **~4 sekund**ga qisqardi.

> **🚀 Motivatsiya:** Modullarga bo'lishning asosiy yutug'i — **selektiv kompilyatsiya**. Siz o'zgartirgan feature'ni va faqat unga kerakli qismlarnigina kompilyatsiya qilasiz, butun ilovani emas. Butun ilova faqat eng oxirida, integratsiya paytida build bo'ladi. Bir qatorlik tuzatishni 45 s o'rniga 4 s'da ko'rish — bu kuniga o'nlab marta takrorlanadigan tsikl.

Diqqat: Asana ogohlantiradi — **bitta katta `AsanaCore` modulini ajratish o'zi hech qanday vaqt tejamadi**; yutuq feature'larni mustaqil modullarga bo'lishdan keldi. Ya'ni "modularizatsiya" degani "bir nechta katta bo'lak" emas, balki mustaqil, kichik feature modullari degani. Buni qanday to'g'ri qilishni TMA o'rgatadi.

> **⚠️ Diqqat:** Modularizatsiya tekin emas. Asana sanab o'tgan narxlar: `git checkout` 5–15 sekund sekinlashdi (chunki checkout'da loyiha qayta generatsiya qilinadi), minglab simvolni skript bilan `public` qilishga to'g'ri keldi, va migratsiya ~3 oy davom etdi. Bu darsning oxirida bu kamchiliklarga qaytamiz.

---

## 2. TMA nima va "µFeature" qayerdan keldi? {#tma-nima}

**TMA (The Modular Architecture)** — Tuist jamoasi tavsiya qiladigan, ilovani **mustaqil feature**lardan qurish usuli. Bu feature'lar bir-biri bilan **aniq va qisqa API**lar orqali bog'lanadi. TMA'ning rasmiy hujjatidagi ta'rif:

> "Its core idea is to build your apps by building independent features that are interconnected using clear and concise APIs."

Avval bu arxitektura **µFeatures** (micro features) deb atalardi. Tuist hujjatida aytilishicha, nomi **The Modular Architecture (TMA)** ga o'zgartirildi — chunki yangi nom uning maqsadi va printsiplarini yaxshiroq aks ettiradi. Shuning uchun eski maqolalarda "µFeatures" so'zini uchratsangiz, bu aynan TMA — bir xil narsa.

### Asosiy printsip {#asosiy-printsip}

TMA'ning yuragi bitta jumlada (hujjatdan):

> **Dasturchilar o'z feature'larini tez build qila, test qila va sinab ko'ra olishi kerak — asosiy ilovadan mustaqil ravishda — shu bilan birga Xcode'ning UI preview, code completion va debugging kabi imkoniyatlari ishonchli ishlashi kerak.**

Bu jumlada 3 ta fe'l bor: **build**, **test**, **try** (sinab ko'rish). Va bitta shart: Xcode'ning qulayliklari buzilmasligi kerak. TMA'ning har bir qarori shu maqsadga xizmat qiladi. Keling, strukturani ko'ramiz.

> **🏢 Real-world:** TMA faqat kichik jamoalar uchun emas. **Toss** (2026-yil case study) ~**1000 modul**ni yuritadi va ularning aksariyati aynan Interface / Implementation / Model / Testing target'larga bo'lingan. Ya'ni bu dars o'rgatadigan ajratish — eng katta iOS loyihalarda ham ishlaydigan naqsh. (Manba: Tuist case study, "Toss: Building less to ship faster".)

---

## 3. Modul anatomiyasi: 5 ta target {#anatomiya}

TMA'da bitta **feature** = bitta **modul**, va u (Xcode ma'nosidagi) **5 ta target**dan iborat bo'ladi. Rasmiy hujjatdagi jadval:

| Target                 | Bog'liqligi                 | Mazmuni                                                                        |
| ---------------------- | --------------------------- | ------------------------------------------------------------------------------ |
| **`Feature`** (Source) | `FeatureInterface`          | Feature'ning asosiy kodi va resurslari (view, view model, service, rasm, font) |
| **`FeatureInterface`** | —                           | Public interface va modellar (protocol'lar, struct'lar)                        |
| **`FeatureTests`**     | `Feature`, `FeatureTesting` | Unit va integration testlar                                                    |
| **`FeatureTesting`**   | `FeatureInterface`          | Test ma'lumotlari va mock'lar — boshqa feature'lar ham ishlatadi               |
| **`FeatureExample`**   | `FeatureTesting`, `Feature` | Example (demo) ilova                                                           |

![µFeature modulining 5 ta target'i va ularning ichki bog'liqliklari: Interface — poydevor, Source va Testing unga, Tests va Example esa Source va Testing'ga tayanadi](/lessons/images/tuist/tuist_06_ufeature_anatomy.svg)

Diagrammada ko'rinib turibdi: **Interface** — poydevor, u hech kimga bog'lanmaydi. **Source** (implementatsiya) va **Testing** (mock'lar) Interface'ga tayanadi. **Tests** va **Example** esa Source va Testing'dan foydalanadi. Endi har birini alohida ko'rib chiqamiz.

### Interface — public yuza {#interface-target}

Bu target faqat **public protocol va model**larni saqlaydi — feature'ning "shartnomasi" (contract). Boshqa modullar aynan shu target bilan gaplashadi, implementatsiyani umuman ko'rmaydi. Masalan, `CatalogInterface`da `CatalogServing` protokoli va `Product` modeli bo'ladi, lekin ularni qanday amalga oshirish — `Catalog` (Source) target'ida, yashirin.

### Source — implementatsiya {#source-target}

Feature'ning haqiqiy kodi: view, view model, service, resurslar. U o'z Interface'ini implement qiladi va boshqa modullarning **Interface**lariga bog'lanadi (Source'lariga emas — bu keyingi bo'limning mavzusi).

### Testing — qayta ishlatiladigan mock'lar {#testing-target}

Bu — TMA'ning eng ko'p e'tibordan chetda qoladigan, lekin eng foydali qismi. `FeatureTesting` target'i **mock'lar va test ma'lumotlari**ni saqlaydi. Muhimi: bu alohida target bo'lgani uchun, uni **boshqa modullar ham import qila oladi**. Masalan, `Home` feature'ining testi `Catalog`ning haqiqiy implementatsiyasini emas, `CatalogTesting`dagi tayyor mock'ni ishlatadi. Shu dars davomida buni real kodda ko'rasiz.

> **💡 Pro tip:** Hujjat yana bitta foydadan eslatadi — `Feature` target'i `FeatureTesting`ni **Development Asset** sifatida ulasa, SwiftUI Preview'lar mock ma'lumot bilan ishlaydi. Ya'ni Preview'da real server kerak emas, mock'dan ko'rsatadi.

### Tests — unit/integration testlar {#tests-target}

Oddiy test target. `@testable import Feature` bilan implementatsiyani ichidan ko'radi, mock'ni `FeatureTesting`dan oladi.

### Example — mustaqil mini-ilova {#example-target}

`FeatureExample` — kichik, mustaqil `.app`. U faqat shu feature'ni ishga tushiradi, mock ma'lumot bilan. Dasturchi **butun ilovani build qilmasdan**, faqat shu feature'ni simulyatorda ochib ko'radi. Turli til, ekran o'lchami, holatlarni shu yerda sinaydi. Bu TMA'ning "try" (sinab ko'rish) qismini amalga oshiradi.

> **⚠️ Diqqat (hujjatdan):** 5 ta target o'rniga, `Testing` kodini compiler direktivalari (`#if DEBUG`) bilan `Feature` yoki `FeatureInterface` ichiga qo'yish mumkin — grafik soddalashadi. Lekin unda ilovaga kerak bo'lmagan test kodini ham kompilyatsiya qilasiz. Tuist buni tavsiya qilmaydi: mock'lar alohida target'da tursa, release build'ga tushmaydi.

---

## 4. Asosiy qoida: Interface'ga bog'lan, Source'ga emas {#qoida}

TMA'ning butun kuchi bitta qoidada: **modul boshqa modulga bog'lansa, uning Source'iga emas, Interface'iga bog'lanadi.**

```text
❌ NOTO'G'RI:  Home  →  Catalog         (Source'ga — implementatsiyaga)
✅ TO'G'RI:    Home  →  CatalogInterface (Interface'ga — shartnomaga)
```

Nega bu shunchalik muhim? Ikkita sabab bor, va ikkalasi ham o'lchash mumkin.

**1-sabab: coupling (bog'lanish) kamayadi.** Agar `Home` to'g'ridan-to'g'ri `Catalog`ning implementatsiyasiga bog'lansa, u `Catalog`ning ichki detallarini biladi — bu kuchli bog'lanish. `CatalogInterface`ga bog'lansa, `Home` faqat protocol'ni (shartnomani) biladi, ichkarida nima bo'layotgani uni qiziqtirmaydi. Bu — Dependency Inversion printsipi.

**2-sabab: clean build tezlashadi.** Hujjatdan to'g'ridan-to'g'ri:

> "It speeds up clean builds because they only have to compile the implementation of our feature, and the interfaces of direct and transitive dependencies."

Ya'ni `Home`ni build qilish uchun kompilyator `Home`ning implementatsiyasini + bog'liq modullarning faqat **Interface**larini kompilyatsiya qiladi, ularning og'ir implementatsiyalarini emas. Interface target odatda bir necha protocol va struct — juda tez compile bo'ladi.

Bu g'oya yangi emas. Tuist hujjati uni **SwiftRock**ning "Reducing iOS Build Times by using Interface Modules" maqolasiga bog'laydi — interface modullari orqali iOS build vaqtini kamaytirish g'oyasi aynan shu yerdan.

![App qatlami Feature'larni, Feature'lar bir-birining Interface'ini, hammasi Core Interface'ini ishlatadigan qatlamli arxitektura](/lessons/images/tuist/tuist_06_layers.svg)

Diagrammada eng muhimi — sariq strelka: `Home` (Source) `CatalogInterface`ga boradi, `Catalog` (Source) ga emas. `App` esa boshqacha — u **composition root** bo'lgani uchun barcha Source (implementatsiya) modullarni yig'adi.

### To'liq bog'lanish qoidalari jadvali {#qoida-jadval}

Bizning loyihamizdagi (quyida quramiz) real bog'lanishlar:

| Kimdan            | Kimga                                        | Nega                                                      |
| ----------------- | -------------------------------------------- | --------------------------------------------------------- |
| `Home` (Source) → | `CatalogInterface`                           | Catalog'ni faqat shartnoma orqali biladi                  |
| `Home` (Source) → | `DesignSystemInterface`                      | Theme'ni protocol orqali oladi                            |
| `Home` (Source) → | `HomeInterface`                              | o'z yuzasini (HomeScreenFactory) implement qiladi         |
| `HomeTesting` →   | `HomeInterface`                              | mock protocol'ni implement qiladi                         |
| `HomeTests` →     | `Home`, `CatalogInterface`, `CatalogTesting` | implementatsiyani test qiladi, Catalog mock'ini ishlatadi |
| `TmaDemo` (App) → | barcha Source + Interface                    | composition root — implementatsiyalarni yig'adi           |

> **🧠 Esda tuting:** Faqat **App** (yoki boshqa "entry point" — App Clip, extension) implementatsiya modullariga bog'lanadi, chunki kimdir oxir-oqibat `CatalogService()` ni yaratishi kerak. Oddiy feature'lar esa doim Interface orqali gaplashadi. Shuning uchun App'ni "composition root" (yig'ish nuqtasi) deymiz.

---

## 5. Loyihani quramiz: ProjectDescriptionHelpers factory {#factory}

Har bir feature uchun 5 ta target'ni qo'lda yozish — zerikarli va xatoga moyil. Agar 10 ta feature bo'lsa, 50 ta target yozasiz, har birida bir xil `destinations`, `bundleId` naqshi, `sources` yo'li. Buni **DRY** qilib, bitta funksiyaga aylantiramiz.

Tuist buning uchun **project description helpers** beradi: `Tuist/ProjectDescriptionHelpers/` papkasidagi barcha `.swift` fayllar bitta `ProjectDescriptionHelpers` moduliga kompilyatsiya qilinadi, va uni manifestlarda `import` qilish mumkin.

> **💡 Pro tip (hujjatdan):** Ko'p tashkilotlar Tuist'ni aynan shuning uchun yaxshi ko'radi — project description helpers platform jamoasiga o'z konvensiyalarini **Swift**da kodlashtirish imkonini beradi. YAML asosidagi generatorlarda buning uchun alohida shablon tili o'ylab topishga to'g'ri keladi; bu yerda — oddiy Swift funksiyasi.

Mana bizning `Module.feature(...)` factory'si. U feature nomini oladi va 5 ta `Target` qaytaradi:

```swift
// Tuist/ProjectDescriptionHelpers/Module.swift
import ProjectDescription

// Bitta "feature" = 5 ta target: Interface, Source, Testing, Tests, Example.
// Bu factory har bir feature uchun shu 5 talikni bir xil qoida bilan yasaydi,
// shunda Project.swift qisqa qoladi va konvensiya butun loyihada bir xil bo'ladi.
public enum Module {
    public static func feature(
        name: String,
        // Source target boshqa modullarning Interface'lariga bog'lanadi.
        dependencies: [TargetDependency] = [],
        // Interface'ning o'zi ba'zan boshqa Interface'ga bog'lanadi (umumiy modellar).
        interfaceDependencies: [TargetDependency] = [],
        // Tests'ga qo'shimcha (masalan boshqa modulning Testing mock'lari).
        testDependencies: [TargetDependency] = [],
        // Example app'ga konkret implementatsiyalar kerak bo'ladi.
        exampleDependencies: [TargetDependency] = [],
        hasExample: Bool = true
    ) -> [Target] {
        var targets: [Target] = []

        // 1. Interface — faqat public protocol va model. Hech kimga bog'lanmaydi (yoki boshqa Interface'ga).
        targets.append(.target(
            name: "\(name)Interface",
            destinations: .iOS,
            product: .framework,
            bundleId: "uz.azimjondev.tma.\(name)Interface",
            deploymentTargets: .iOS("17.0"),
            sources: ["Modules/\(name)/Interface/**"],
            dependencies: interfaceDependencies
        ))

        // 2. Source — implementatsiya. Faqat o'z Interface'iga + boshqa modul Interface'lariga.
        targets.append(.target(
            name: name,
            destinations: .iOS,
            product: .framework,
            bundleId: "uz.azimjondev.tma.\(name)",
            deploymentTargets: .iOS("17.0"),
            sources: ["Modules/\(name)/Sources/**"],
            dependencies: [.target(name: "\(name)Interface")] + dependencies
        ))

        // 3. Testing — mock'lar va test ma'lumotlari. Boshqa modullar ham import qila oladi.
        targets.append(.target(
            name: "\(name)Testing",
            destinations: .iOS,
            product: .framework,
            bundleId: "uz.azimjondev.tma.\(name)Testing",
            deploymentTargets: .iOS("17.0"),
            sources: ["Modules/\(name)/Testing/**"],
            dependencies: [.target(name: "\(name)Interface")]
        ))

        // 4. Tests — unit testlar. @testable orqali modulning o'zi ko'rinadi.
        // Interface yoki mock kerak bo'lsa, test aynan nimani import qilsa, shuni
        // testDependencies'da aniq sanab beramiz.
        targets.append(.target(
            name: "\(name)Tests",
            destinations: .iOS,
            product: .unitTests,
            bundleId: "uz.azimjondev.tma.\(name)Tests",
            deploymentTargets: .iOS("17.0"),
            sources: ["Modules/\(name)/Tests/**"],
            dependencies: [.target(name: name)] + testDependencies
        ))

        // 5. Example — feature'ni butun ilovasiz, mock bilan alohida run qilish uchun kichik app.
        if hasExample {
            targets.append(.target(
                name: "\(name)Example",
                destinations: .iOS,
                product: .app,
                bundleId: "uz.azimjondev.tma.\(name)Example",
                deploymentTargets: .iOS("17.0"),
                infoPlist: .extendingDefault(with: ["UILaunchScreen": [:]]),
                sources: ["Modules/\(name)/Example/**"],
                dependencies: [.target(name: name)] + exampleDependencies
            ))
        }

        return targets
    }
}
```

> **🧠 Esda tuting:** Factory har bir target uchun `product` ni aniq belgilaydi: Interface/Source/Testing — `.framework`, Tests — `.unitTests`, Example — `.app`. Bu `Product` enum'ining real case'lari (Tuist `ProjectDescription/Product.swift` manbasidan): `app`, `framework`, `staticFramework`, `staticLibrary`, `dynamicLibrary`, `unitTests`, `uiTests`, `bundle`, `macro`, `appExtension`, `appClip` va boshqalar.

Endi `Project.swift` qisqa va o'qiladigan bo'ladi — har bir feature bitta chaqiruv:

```swift
// Project.swift
import ProjectDescription
import ProjectDescriptionHelpers

let project = Project(
    name: "TmaDemo",
    targets:
        // DesignSystem — core modul, example shart emas.
        Module.feature(
            name: "DesignSystem",
            testDependencies: [
                .target(name: "DesignSystemInterface"),
                .target(name: "DesignSystemTesting"),
            ],
            hasExample: false
        )
        +
        // Catalog — DesignSystem'ning faqat Interface'iga bog'lanadi.
        Module.feature(
            name: "Catalog",
            dependencies: [.target(name: "DesignSystemInterface")],
            testDependencies: [
                .target(name: "CatalogInterface"),
                .target(name: "CatalogTesting"),
            ],
            exampleDependencies: [
                .target(name: "CatalogTesting"),
                .target(name: "DesignSystem"),
            ]
        )
        +
        // Home — Catalog'ning faqat Interface'iga bog'lanadi (muhim: Source'iga EMAS).
        Module.feature(
            name: "Home",
            dependencies: [
                .target(name: "CatalogInterface"),
                .target(name: "DesignSystemInterface"),
            ],
            testDependencies: [
                .target(name: "CatalogInterface"),
                .target(name: "CatalogTesting"),
            ],
            exampleDependencies: [
                .target(name: "Catalog"),
                .target(name: "DesignSystem"),
            ]
        )
        +
        // App — Source modullarni yig'adi, Home'ni esa Interface orqali oladi (DI).
        [
            .target(
                name: "TmaDemo",
                destinations: .iOS,
                product: .app,
                bundleId: "uz.azimjondev.tma.app",
                deploymentTargets: .iOS("17.0"),
                infoPlist: .extendingDefault(with: ["UILaunchScreen": [:]]),
                sources: ["App/Sources/**"],
                dependencies: [
                    .target(name: "Home"),
                    .target(name: "HomeInterface"),
                    .target(name: "Catalog"),
                    .target(name: "CatalogInterface"),
                    .target(name: "DesignSystem"),
                    .target(name: "DesignSystemInterface"),
                ]
            ),
        ]
)
```

E'tibor bering: `Module.feature(...)` `[Target]` qaytaradi, shuning uchun ularni `+` bilan qo'shamiz va App target'ini oddiy massiv sifatida ulaymiz.

### Papka tuzilmasi {#papka}

Factory `sources: ["Modules/\(name)/..."]` deb kutganidek, papkalarni shunday tashkil qilamiz:

```bash
# Har bir feature uchun 5 ta kichik papka
for m in DesignSystem Catalog Home; do
  mkdir -p Modules/$m/{Interface,Sources,Testing,Tests,Example}
done
mkdir -p App/Sources
```

Natijada loyiha shunday ko'rinadi:

```text
TmaDemo/
├── Tuist.swift
├── Tuist/
│   ├── Package.swift
│   └── ProjectDescriptionHelpers/
│       └── Module.swift          ← factory shu yerda
├── Project.swift
├── App/
│   └── Sources/
│       ├── TmaDemoApp.swift
│       └── AppDependencies.swift  ← composition root
└── Modules/
    ├── DesignSystem/
    │   ├── Interface/   Sources/   Testing/   Tests/
    ├── Catalog/
    │   ├── Interface/   Sources/   Testing/   Tests/   Example/
    └── Home/
        ├── Interface/   Sources/   Testing/   Tests/   Example/
```

Minimal `Tuist.swift` (2026 holati — eski `Config.swift` o'rniga):

```swift
// Tuist.swift
import ProjectDescription

let tuist = Tuist(project: .tuist())
```

Generatsiya qilamiz:

```bash
tuist generate --no-open
```

Natija (real, bizning mashinamizda):

```text
Loading and constructing the graph
Generating workspace TmaDemo.xcworkspace
Generating project TmaDemo
Total time taken: 1.751s
✔ Success
  Project generated.
```

1.7 sekundda `.xcworkspace`, `.xcodeproj` va `Derived/` papkasi tayyor. Generatsiya qilingan fayllarni git'ga qo'ymaymiz (`.gitignore`ga `*.xcodeproj`, `*.xcworkspace`, `Derived/`, `.build/`). `tuist generate`, `tuist graph`, `tuist edit` va boshqa buyruqlar haqida to'liq ma'lumotni [03. Papka tuzilmasi va CLI](/ios-lessons/tuist-03-directory-structure) darsida ko'rishingiz mumkin.

---

## 6. Modullar kodi: Interface, Source, Testing, Tests, Example {#modul-kodi}

Endi real kod. Uchta modulimiz bor: `DesignSystem` (core), `Catalog` va `Home` (feature'lar). To'liq zanjirni `Catalog`da ko'rsatamiz.

### CatalogInterface — shartnoma {#catalog-interface}

```swift
// Modules/Catalog/Interface/CatalogInterface.swift
import Foundation

// Catalog'ning public modeli va protokoli. Home faqat shu faylga bog'lanadi.
public struct Product: Identifiable, Equatable, Sendable {
    public let id: String
    public let name: String
    public let price: Int   // so'mda butun son

    public init(id: String, name: String, price: Int) {
        self.id = id
        self.name = name
        self.price = price
    }
}

public protocol CatalogServing: Sendable {
    func featured() -> [Product]
}
```

> **🧠 Esda tuting:** Interface'dagi hamma narsa `public` bo'lishi SHART — boshqa modullar ko'rishi kerak. Va modellarning `init`i ham `public` (Swift'da framework'ning public struct'i avtomatik public memberwise init olmaydi). Buni unutsangiz, boshqa modulda `Product(...)` yaratib bo'lmaydi degan xato olasiz.

### Catalog (Source) — implementatsiya {#catalog-source}

```swift
// Modules/Catalog/Sources/CatalogService.swift
import CatalogInterface
import Foundation

// Implementatsiya. Bu faylni o'zgartirsak CatalogInterface o'zgarmaydi.
public struct CatalogService: CatalogServing {
    public init() {}

    public func featured() -> [Product] {
        [
            Product(id: "1", name: "Dasturlash asoslari", price: 120_000),
            Product(id: "2", name: "Swift tili", price: 150_000),
            Product(id: "3", name: "iOS arxitektura", price: 180_000),
        ]
    }
}
```

```swift
// Modules/Catalog/Sources/CatalogView.swift
import CatalogInterface
import DesignSystemInterface
import SwiftUI

public struct CatalogView: View {
    private let service: CatalogServing
    private let theme: ThemeProviding

    public init(service: CatalogServing, theme: ThemeProviding) {
        self.service = service
        self.theme = theme
    }

    public var body: some View {
        List(service.featured()) { product in
            HStack {
                Text(product.name)
                Spacer()
                Text("\(product.price) so'm")
                    .foregroundStyle(theme.accentColor)
            }
        }
    }
}
```

`CatalogView` `DesignSystemInterface`dan `ThemeProviding`ni oladi — yana Interface orqali. `DesignSystem`ning haqiqiy `DefaultTheme`ini u ko'rmaydi.

### CatalogTesting — qayta ishlatiladigan mock {#catalog-testing}

```swift
// Modules/Catalog/Testing/StubCatalogService.swift
import CatalogInterface

// Oldindan tayyor mock — Home testlari ham, Example app ham shuni ishlatadi.
public struct StubCatalogService: CatalogServing {
    private let products: [Product]
    public init(products: [Product] = [
        Product(id: "t1", name: "Test kitob", price: 1000),
        Product(id: "t2", name: "Mock kitob", price: 2000),
    ]) {
        self.products = products
    }
    public func featured() -> [Product] { products }
}
```

### CatalogTests {#catalog-tests}

Bu yerda **Swift Testing** (yangi `@Test` / `#expect` sintaksisi, Xcode 27'da standart) ishlatamiz:

```swift
// Modules/Catalog/Tests/CatalogTests.swift
import Testing
@testable import Catalog
import CatalogInterface
import CatalogTesting

struct CatalogTests {
    @Test func serviceReturnsThreeFeaturedProducts() {
        let service = CatalogService()
        #expect(service.featured().count == 3)
    }

    @Test func stubIsConfigurable() {
        let stub = StubCatalogService(products: [Product(id: "x", name: "Bitta", price: 5)])
        #expect(stub.featured().count == 1)
        #expect(stub.featured().first?.name == "Bitta")
    }
}
```

### Home — boshqa modulni faqat Interface orqali ishlatish {#home-source}

Mana TMA qoidasining jonli namunasi. `HomeViewModel` `Catalog`ning implementatsiyasini emas, `CatalogServing` protokolini oladi:

```swift
// Modules/Home/Sources/HomeViewModel.swift
import CatalogInterface
import Foundation
import Observation

// Home Catalog'ning IMPLEMENTATSIYASINI emas, faqat CatalogServing protokolini biladi.
@Observable
public final class HomeViewModel {
    public private(set) var featuredCount: Int = 0
    private let catalog: CatalogServing

    public init(catalog: CatalogServing) {
        self.catalog = catalog
    }

    public func load() {
        featuredCount = catalog.featured().count
    }
}
```

`HomeView` ham xuddi shunday — faqat protokollarni (`CatalogServing`, `ThemeProviding`) oladi:

```swift
// Modules/Home/Sources/HomeView.swift
import CatalogInterface
import DesignSystemInterface
import SwiftUI

public struct HomeView: View {
    @State private var viewModel: HomeViewModel
    private let theme: ThemeProviding

    public init(catalog: CatalogServing, theme: ThemeProviding) {
        _viewModel = State(initialValue: HomeViewModel(catalog: catalog))
        self.theme = theme
    }

    public var body: some View {
        VStack(spacing: Spacing.medium) {
            theme.title("Bosh sahifa")
            Text("Tanlangan mahsulotlar: \(viewModel.featuredCount)")
        }
        .onAppear { viewModel.load() }
    }
}
```

`Home`ning o'z yuzasi (`HomeInterface`) `HomeScreenFactory` protokolini beradi, va `Home` (Source) uni implement qiladi:

```swift
// Modules/Home/Interface/HomeInterface.swift
import SwiftUI

public protocol HomeScreenFactory: Sendable {
    func makeHome() -> AnyView
}
```

```swift
// Modules/Home/Sources/HomeComponent.swift
import CatalogInterface
import DesignSystemInterface
import HomeInterface
import SwiftUI

// App Home'ni faqat HomeInterface orqali biladi, implementatsiyani shu komponent beradi.
public struct HomeComponent: HomeScreenFactory {
    private let catalog: CatalogServing
    private let theme: ThemeProviding

    public init(catalog: CatalogServing, theme: ThemeProviding) {
        self.catalog = catalog
        self.theme = theme
    }

    public func makeHome() -> AnyView {
        AnyView(HomeView(catalog: catalog, theme: theme))
    }
}
```

### HomeTests — boshqa modulning mock'ini qayta ishlatish {#home-tests}

Bu — `Testing` target'ining asl foydasi. `HomeViewModel` `CatalogServing`ga bog'liq, shuning uchun uning testi `Catalog`ning mock'ini (`CatalogTesting`dagi `StubCatalogService`) ishlatadi — `Catalog`ning haqiqiy implementatsiyasi yoki server kerak emas:

```swift
// Modules/Home/Tests/HomeViewModelTests.swift
import Testing
@testable import Home
import CatalogInterface
import CatalogTesting

struct HomeViewModelTests {
    @Test func loadCountsFeaturedFromCatalog() {
        // Boshqa modulning Testing mock'ini qayta ishlatamiz — haqiqiy Catalog kerak emas.
        let vm = HomeViewModel(catalog: StubCatalogService())
        vm.load()
        #expect(vm.featuredCount == 2)
    }

    @Test func emptyCatalogGivesZero() {
        let vm = HomeViewModel(catalog: StubCatalogService(products: []))
        vm.load()
        #expect(vm.featuredCount == 0)
    }
}
```

Testlarni ishga tushiramiz (real natija, iPhone 17 / iOS 27.0 simulyatorda):

```bash
tuist test Home --device "iPhone 17" --os 27.0
```

```text
Test session results:
Test Succeeded
✔ Success
  The project tests ran successfully
```

> **💡 Pro tip:** `tuist test Home` — bu yerda `Home` bu **scheme** nomi, test target emas. Tuist har bir modul uchun avtomatik scheme yaratadi va test target'ni shu scheme'ga biriktiradi. `tuist test HomeTests` deb yozsangiz, "Couldn't find scheme HomeTests" xatosini olasiz — mavjud scheme'lar `tuist test` chiqarib beradi.

---

## 7. Dependency injection: implementatsiyani bir joyda yig'ish {#di}

Modullar bir-birini faqat Interface orqali biladi. Lekin ilova ishga tushganda kimdir haqiqiy `CatalogService()` ni yaratishi va `Home`ga berishi kerak. Bu "implementatsiya grafigini qurish" App'da, bitta joyda — **composition root**da bo'ladi.

TMA hujjati bu haqda ataylab fikr bildirmaydi ("non-opinionated"), lekin tavsiya beradi:

> "We recommend using dependency-injection solutions or patterns that don't add built-time indirections or use platform APIs that were not designed for this purpose."

Ya'ni og'ir DI framework'lar (build vaqtida kod generatsiya qiladigan) yoki platformani noto'g'ri ishlatadigan "hiyla"lar shart emas. Bizda eng oddiy, qo'lda DI yetarli:

```swift
// App/Sources/AppDependencies.swift
import Catalog
import CatalogInterface
import DesignSystem
import DesignSystemInterface
import Home
import HomeInterface

// Implementatsiya grafigi bitta joyda yig'iladi — "composition root".
// Modullar bir-birini Interface orqali biladi; konkret tiplar faqat shu yerda uchrashadi.
struct AppDependencies {
    let theme: ThemeProviding = DefaultTheme()
    let catalog: CatalogServing = CatalogService()
    var home: HomeScreenFactory { HomeComponent(catalog: catalog, theme: theme) }
}
```

E'tibor bering: `AppDependencies` bu — loyihada **yagona** joy bo'lib, u ham `Catalog` (implementatsiya), ham `CatalogInterface` (shartnoma)ni import qiladi. Konkret tiplar (`DefaultTheme`, `CatalogService`, `HomeComponent`) faqat shu yerda nomlanadi. Boshqa barcha modullar faqat protokollarni biladi.

```swift
// App/Sources/TmaDemoApp.swift
import Catalog
import SwiftUI

@main
struct TmaDemoApp: App {
    private let deps = AppDependencies()

    var body: some Scene {
        WindowGroup {
            TabView {
                // Home faqat Interface orqali keladi (HomeScreenFactory.makeHome()).
                deps.home.makeHome()
                    .tabItem { Label("Bosh", systemImage: "house") }

                CatalogView(service: deps.catalog, theme: deps.theme)
                    .tabItem { Label("Katalog", systemImage: "books.vertical") }
            }
        }
    }
}
```

`deps.home.makeHome()` — App `Home`ning ichki `HomeView`ini bilmaydi ham; u faqat `HomeScreenFactory` protokolini chaqiradi. Agar ertaga `Home`ning butun ichki tuzilishini qayta yozsangiz, App'dagi bu qator o'zgarmaydi — chunki shartnoma o'sha-o'sha.

> **🧠 Esda tuting:** Bu DI shaklida yangi dasturchi uchun katta afzallik bor: "Bu `CatalogServing` qayerdan kelyapti?" degan savolga javob doim bitta joyda — `AppDependencies`da. Implementatsiyani almashtirish (masalan, test uchun `StubCatalogService`ga) ham shu yerda, bitta qatorda.

---

## 8. Statik va dinamik: product type va dynamic configuration {#product-type}

Factory'da har bir modulni `.framework` (dinamik) qilib belgiladik. Nega? Chunki TMA development uchun **dinamik**, release uchun **statik**ni tavsiya qiladi. Hujjatdan:

> "We recommend using dynamic libraries or frameworks during development ... This is key for fast compilation times and to ensure SwiftUI Previews work reliably. And static libraries or frameworks for the release builds to ensure the app boots fast."

Qisqa qilib:

|                               | Dinamik (`.framework`)     | Statik (`.staticFramework`) |
| ----------------------------- | -------------------------- | --------------------------- |
| Incremental build             | tez                        | sekinroq                    |
| SwiftUI Preview               | ishonchli ishlaydi         | muammoli bo'lishi mumkin    |
| Ilova ishga tushishi (launch) | sekinroq (dylib yuklanadi) | tez (bitta binary)          |
| Ishlatish vaqti               | **development**            | **release**                 |

Buni har safar qo'lda o'zgartirmaslik uchun Tuist **dynamic configuration** beradi: generatsiya vaqtida environment o'zgaruvchisini o'qiysiz. Hujjatdagi naqsh:

```swift
// Tuist/ProjectDescriptionHelpers ichida
func productType() -> Product {
    if case let .string(productType) = Environment.productType {
        return productType == "static-library" ? .staticLibrary : .framework
    } else {
        return .framework   // default: dinamik
    }
}
```

```bash
# Release uchun statik qilib generatsiya:
TUIST_PRODUCT_TYPE=static-library tuist generate
# Development uchun oddiy (dinamik):
tuist generate
```

`Environment.productType` — `TUIST_PRODUCT_TYPE` environment o'zgaruvchisini o'qiydi (Tuist `TUIST_` prefiksli o'zgaruvchilarni `Environment`ga uzatadi). Factory'dagi `product: .framework` o'rniga `product: productType()` yozsangiz, bitta loyiha ikkala rejimda ham ishlaydi.

> **🏢 Real-world:** Bu nazariya emas. **Monzo** (Tuist case study, 2026-05-26) aynan shunday qiladi: "tests use dynamic frameworks and release uses static". Ya'ni test va development dinamik, App Store build'i statik. Shu bilan ham tez iteratsiya, ham tez launch oladi.

> **⚠️ Diqqat — mergeable libraries:** Apple statik/dinamik almashish muammosini "mergeable libraries" bilan yengillashtirmoqchi bo'lgan. Lekin TMA hujjati buni **tavsiya qilmaydi**: u build vaqtida noaniqlik (non-determinism) kiritadi, build'ni takrorlanmas va optimallashtirilishi qiyin qiladi. Statik/dinamikni `Environment` orqali aniq boshqargan afzal.

---

## 9. O'lchov: Interface o'zgarmasa, dependent qayta qurilmaydi {#olchov}

Endi TMA'ning asosiy va'dasini **o'lchaymiz**. Da'vo shunday edi: modul boshqa modulning Interface'iga bog'lansa, o'sha boshqa modulning implementatsiyasi o'zgarganda — bizning modul qayta kompilyatsiya bo'lmaydi.

Bizning grafik: `Home` → `CatalogInterface` (Source'ga emas). Tekshiramiz. Avval loyiha grafigini `tuist graph` bilan tasdiqlaymiz:

```bash
tuist graph --format json --skip-test-targets
```

Natijadan (bog'liqlik qirralari):

```text
Home     ->  [HomeInterface, CatalogInterface, DesignSystemInterface]
Catalog  ->  [CatalogInterface, DesignSystemInterface]
TmaDemo  ->  [DesignSystemInterface, Catalog, DesignSystem, Home, CatalogInterface, HomeInterface]
```

`Home` haqiqatan `CatalogInterface`ga bog'langan, `Catalog`ga emas. Endi 3 ta ssenariy bo'yicha `xcodebuild`ni o'lchaymiz va **qaysi target'lar Swift'ni qayta kompilyatsiya qilganini** sanab chiqamiz (`xcodebuild` log'idagi `SwiftDriver Compilation <Target>` qatorlari orqali).

**1-ssenariy — implementatsiya o'zgardi.** `Catalog/Sources/CatalogService.swift` ichidagi bitta mahsulot nomi va narxini o'zgartiramiz (public API tegmaydi):

```bash
xcodebuild -workspace TmaDemo.xcworkspace -scheme TmaDemo \
  -destination 'platform=iOS Simulator,name=iPhone 17,OS=27.0' \
  -derivedDataPath .dd build
```

Natija:

```text
** BUILD SUCCEEDED **
Qayta kompilyatsiya qilingan target'lar: Catalog
```

Faqat **`Catalog`**! `Home` tegilmadi, `TmaDemo` (App) ning Swift'i ham tegilmadi. Chunki `CatalogInterface.swiftmodule` o'zgarmagan — `Home` faqat shunga qaraydi.

**2-ssenariy — Interface o'zgardi.** `Catalog/Interface/CatalogInterface.swift` dagi `Product`ga yangi public maydon qo'shamiz:

```swift
public let category: String   // yangi public maydon
```

Xuddi shu build buyrug'i. Natija:

```text
** BUILD SUCCEEDED **
Qayta kompilyatsiya qilingan target'lar: CatalogInterface, Catalog, Home, TmaDemo
```

Bu safar **to'rtta** target — butun zanjir. `CatalogInterface` o'zgargani uchun, unga bog'liq hamma (Catalog, Home, App) qayta kompilyatsiya bo'ldi.

![Ikki ssenariy yonma-yon: implementatsiya o'zgarsa faqat Catalog qayta quriladi (Home tegilmaydi); Interface o'zgarsa butun zanjir qayta quriladi](/lessons/images/tuist/tuist_06_interface_rebuild.svg)

To'liq o'lchov jadvali (Apple M4 Pro, 12 core, 24 GB; Xcode 27.1; Tuist 4.211.1; iPhone 17 / iOS 27.0 simulyator; `-derivedDataPath .dd`):

| Ssenariy                               | Wall vaqt | Qayta kompilyatsiya bo'lgan target'lar           |
| -------------------------------------- | --------- | ------------------------------------------------ |
| Cold build (DerivedData tozalangan)    | 11.5 s    | hamma 7 ta Swift target                          |
| Null build (hech narsa o'zgarmagan)    | 1.8 s     | — (hech biri)                                    |
| **Catalog implementatsiyasi** o'zgardi | 2.3 s     | **faqat `Catalog`**                              |
| **CatalogInterface** o'zgardi          | 3.1 s     | `CatalogInterface`, `Catalog`, `Home`, `TmaDemo` |

### Mexanizm: Xcode nega shunday qaror qiladi? {#mexanizm}

Har bir Swift target build bo'lganda `.swiftmodule` fayli yaratiladi — bu uning **public interface**ining "surati". Target'ning dependent'i (unga bog'liq modul) faqat shu `.swiftmodule`ni ko'radi. Xcode incremental build'da: agar dependency'ning `.swiftmodule`i o'zgarmagan bo'lsa, dependent'ni qayta kompilyatsiya qilmaydi.

- `Catalog`ning **implementatsiyasi**ni o'zgartirganda, `CatalogInterface.swiftmodule` o'zgarmaydi (u alohida target). `Home` faqat `CatalogInterface`ga qaragani uchun — tegilmaydi.
- `Catalog`ning **Interface**ini o'zgartirganda, `CatalogInterface.swiftmodule` o'zgaradi — unga bog'liq hamma qayta kompilyatsiya bo'ladi.

Agar `Home` to'g'ridan-to'g'ri `Catalog` (Source) ga bog'langanida edi, `Catalog`ning har bir implementatsiya o'zgarishi `Catalog.swiftmodule`ni o'zgartirardi (public simvollar o'sha modulda), va `Home` har safar qayta kompilyatsiya bo'lardi. Interface'ni ajratish aynan shuni oldini oladi.

> **📊 Real raqamlar:** Bizning loyiha kichik (7 target), shuning uchun wall vaqtlar yaqin — vaqt asosan `xcodebuild`ning rejalashtirish va linklashiga ketadi, kompilyatsiyaga emas. **Yutuq modul soni oshgani sayin kattalashadi.** Asana'da (2023-02-21 blog) µFeature ajratishdan keyin Inbox modulidagi bir qatorlik o'zgarish **30–50 s → ~4 s** ga tushgan. Ya'ni bu yerdagi "faqat 1 target qayta quriladi" tamoyili, 300 ming satrli loyihada sekundlarni daqiqalarga aylantiradi.

> **⚠️ Diqqat — o'lchash metodikasi:** Bitta o'lchov ishonchli emas. `intrada` open-source loyihasi (2026-09-30) build cache'ni tekshirganda to'g'ri metodikani ko'rsatgan: bitta build buyrug'ini tanlang, har safar bitta qatorlik o'zgarish kiriting, har rejimda 10 marta ishga tushiring, birinchisini (warm-up) tashlab yuboring, medianni oling, va mashina yuki (load average) past bo'lganda boshlang. Biz bu yerda target-larning **qaysi biri** qayta kompilyatsiya bo'lishini (bu deterministik) ko'rsatdik; aniq sekundlar mashinaga va yukka qarab o'zgaradi.

---

## 10. Chegarani majburlash: tuist inspect dependencies {#inspect}

TMA qoidasini qo'lda eslab yurish qiyin. Birov `Home`da `import Catalog` (implementatsiya!) deb yozib yuborsa-chi? Loyiha baribir compile bo'lishi mumkin, chunki `Catalog` grafik ichida transitiv mavjud. Bu "implicit dependency" — e'lon qilinmagan, lekin ishlatilayotgan bog'liqlik. Ular TMA chegarasini sekin-asta buzadi.

> **🧠 Esda tuting:** Eski `Tuist.swift` dagi `enforceExplicitDependencies` opsiyasi 4.211.1 da **deprecated**. Uning o'rniga alohida buyruq bor: `tuist inspect dependencies`.

Men bu loyihani yozganimda, birinchi urinishda aynan shu buyruq meni ushladi. `tuist inspect dependencies --only implicit` ishga tushirdim va mana bu xatoni oldim (real natija):

```text
✖ Error
  The following implicit dependencies were found:
   - CatalogTests implicitly depends on: CatalogInterface
   - DesignSystemTests implicitly depends on: DesignSystemInterface
   - HomeTests implicitly depends on: CatalogInterface
   - TmaDemo implicitly depends on: CatalogInterface, DesignSystemInterface
```

Nima bo'lgan edi? Masalan `HomeTests` fayli `import CatalogInterface` qilardi, lekin target'da `CatalogInterface` bog'liqligi aniq e'lon qilinmagan edi — u `CatalogTesting` orqali transitiv kelardi. `TmaDemo` (App) esa `AppDependencies`da `CatalogInterface` va `DesignSystemInterface`ni import qilardi, lekin faqat `Catalog`, `DesignSystem` (Source) ni e'lon qilgan edi.

Yechim — har bir target import qilgan narsani aniq e'lon qilish. `Project.swift`da `testDependencies` va App'ning `dependencies`iga yetishmayotgan Interface'larni qo'shdim (yuqoridagi to'liq `Project.swift`da ularni ko'rasiz). Keyin qayta tekshirdim:

```bash
tuist inspect dependencies
```

```text
We did not find any dependency issues in your project (checked: implicit, redundant).
```

`tuist inspect dependencies` ikki xil muammoni ushlaydi:

- `--only implicit` — e'lon qilinmagan, lekin ishlatilayotgan bog'liqlik (yashirin). Bu **majburan tuzatilishi kerak**: explicit modules va CAS cache faqat implicit bog'liqliksiz ishlaydi.
- `--only redundant` — e'lon qilingan, lekin ishlatilmayotgan bog'liqlik (ortiqcha). Bu tozalik uchun: masalan factory har bir Tests'ga `Testing`ni avtomatik qo'shsa, lekin ba'zi test uni import qilmasa, shu ortiqchalikni ko'rsatadi.

> **🏢 Real-world:** **Toss** (~1000 modul) binary cache ishlashi uchun avval grafikni "cache qilib bo'ladigan" holatga keltirgan: ishlatilmagan import'larni avtomatik olib tashlagan, shartli kompilyatsiya (`#if`) flag'larini umumiy modullardan chiqargan, va **har bir PR'da cache qilib bo'ladigan modullar ulushi tushsa, ogohlantiradigan tekshiruv** qo'shgan. Tuist statik tarzda implicit import'larni aniqlashni tavsiya qiladi — chunki explicit modules va CAS faqat implicit bog'liqliksiz ishlaydi. (Manba: Tuist "Toss" case study + "scale" blog, 2025-09-22.)

Shuning uchun `tuist inspect dependencies`ni CI'da (har bir PR'da) ishga tushirish — TMA chegarasini uzoq muddat saqlashning amaliy usuli.

---

## 11. Example app bilan tez iteratsiya {#example}

TMA'ning "try" (sinab ko'rish) qismi — `Example` app. Uni butun ilovani build qilmasdan ishga tushirasiz. Bizning `CatalogExample`:

```swift
// Modules/Catalog/Example/CatalogExampleApp.swift
import Catalog
import CatalogTesting
import DesignSystem
import SwiftUI

// Butun ilovasiz, faqat Catalog'ni mock ma'lumot bilan ishga tushiruvchi kichik app.
@main
struct CatalogExampleApp: App {
    var body: some Scene {
        WindowGroup {
            CatalogView(service: StubCatalogService(), theme: DefaultTheme())
        }
    }
}
```

E'tibor bering: `StubCatalogService` — `CatalogTesting`dagi mock. Ya'ni Example server kerak qilmaydi, tayyor ma'lumot bilan ishlaydi.

Tuist har bir modulning target'larini bitta scheme'ga guruhlaydi. `Catalog` scheme'ining "run" target'i — aynan `CatalogExample.app`. Uni build qilib ko'ramiz (real o'lchov, DerivedData tozalangan):

```bash
xcodebuild -workspace TmaDemo.xcworkspace -scheme Catalog \
  -destination 'platform=iOS Simulator,name=iPhone 17,OS=27.0' \
  -derivedDataPath .dd-ex build
```

```text
** BUILD SUCCEEDED **  (wall: 6.8 s)
Kompilyatsiya qilingan target'lar:
  Catalog, CatalogInterface, CatalogTesting, DesignSystem, DesignSystemInterface, CatalogExample
```

Diqqat qiling: `Home`, `HomeInterface`, `TmaDemo` **umuman build bo'lmadi**. Catalog feature'ini sinab ko'rish uchun Home yoki butun ilova kerak emas. 6.8 s — to'liq ilovaning cold build'i (11.5 s) dan sezilarli kam, va loyiha o'sgan sari bu farq kattalashadi.

> **🚀 Motivatsiya:** Mana TMA'ning kundalik kuchi: siz "Katalog" ekranida ishlayapsiz. Example app'ni ochasiz, mock ma'lumot bilan 6.8 sekundda simulyatorda ko'rasiz, o'zgartirasiz, yana ko'rasiz — "Bosh sahifa", "Profil", "Savat" va qolgan 50 ta modul sizni sekinlatmaydi. Butun ilovani faqat ish tugab, integratsiya paytida yig'asiz.

---

## 12. Katta miqyosda: Monzo, Toss, monday.com {#miqyos}

Bizning loyiha 3 modul. TMA'ning haqiqiy kuchi yuzlab modulda ko'rinadi. Bu yerdagi raqamlarning **hammasi vendor (Tuist) tomonidan e'lon qilingan** yoki mijoz-muallif case study'lar — ularni shunday o'qing, mustaqil benchmark emas.

> **🏢 Monzo** (kompaniya muallifligidagi, Tuist e'lon qilgan case study, 2026-05-26): bank ilovasi ~**50 dan ~200 modul**gacha o'sgan — 45+ iOS muhandis, 1.9M satr Swift, 20k unit + 8k snapshot test. CI bir paytlar har PR uchun **70 daqiqadan oshgan**. Tuist module cache'ni `main`da har 30 daqiqada "warm" qilib turish bilan: **P50 PR vaqti 52 → 15 daqiqa** (−71%), P90 57 → 35. Hujjatga ko'ra: docs-only PR ~3 daqiqa, feature-modul o'zgarishi ~10 daqiqa, umumiy (shared) modul o'zgarishi (~15k test) ~23 daqiqa. Cache hit ~80%, va **hech qachon production build uchun ishlatilmaydi**; test dinamik framework, release statik. (Manba: Tuist "Monzo" case study.)

> **🏢 Toss** (~1000 modul, 2026-10-01): profiling ko'rsatdiki, Xcode vaqtining katta qismi **dependency grafigini hisoblash**ga ketardi — bu narx o'zgarish hajmiga emas, modul soniga qarab o'sadi. ~3 oyda jamoa "focus" workflow qurdi: dasturchi bitta scheme e'lon qiladi, qolgani oldindan qurilgan XCFramework sifatida (self-hosted Tuist + Kura cache) keladi. 1 daqiqadan tez tugaydigan lokal build'lar ulushi uch barobardan ko'proq oshgan. ~1000 binary'da yangi to'siqlar: framework search path'lar, hash barqarorligi, cache warm narxi. (Manba: Tuist "Toss: Building less to ship faster".)

> **🏢 monday.com** (2025-12-15): ~20 iOS muhandis, ~200 modul; merge CI workflow >20 daqiqadan (2022) ~9 daqiqaga tushgan, "ko'p yaxshilanishlardan", Tuist module cache "muhim rol" o'ynagan. **Delivery Hero** (~115 modul, Logistics jamoasi rider ilovasi): clean build 2.5 → 0.5 daqiqa, CI build+test 2.5 barobar tez. (Manba: Tuist case study'lar, vendor-published.)

Modul soni bo'yicha "chegara" haqida: hech bir manba Tuist yoki TMA uchun aniq jamoa-hajmi yoki modul-soni chegarasini bermaydi. E'lon qilingan "langar"lar: Delivery Hero ~115, monday.com / Monzo ~200 (20–45+ muhandis), Toss ~1000. Umumiy maslahat (Tuist'ning o'zidan): og'riq **o'lchanmaguncha** native toolchain'ga yaqin turing.

---

## 13. Kamchiliklar va qachon TMA kerak emas {#kamchilik}

TMA bepul emas. Halol kamchiliklar (hammasi sourced):

- **Boilerplate va o'rganish egri chizig'i.** Asana (2023-02-21): ~1400 satrli Tuist manifest, "faqat ozchilik" muhandis Tuist ichki ishlashida qulay edi ("3+ Tuist eksperti amalda yetarli"). 5 target × har feature = ko'p fayl; factory yordam beradi, lekin konsepsiya baribir o'rganishni talab qiladi.
- **Checkout sekinlashadi.** Asana: `git checkout` 5–15 sekund sekinlashdi (checkout'da loyiha qayta generatsiya qilinadi).
- **"Modularizatsiya" o'zi yetarli emas.** Asana: bitta katta `AsanaCore` modulini ajratish **hech qanday build vaqti tejamadi** — yutuq feature'larni _mustaqil_ modullarga bo'lishdan keldi. Noto'g'ri bo'lish (bir nechta katta modul) foyda bermaydi.
- **Interface ajratish ko'p `public` talab qiladi.** Boshqa modul ko'radigan har bir tip, funksiya, init `public` bo'lishi kerak. Asana minglab simvolni skript bilan `public` qilgan.
- **Compiler-directive muqobili trade-off bilan.** 5 target o'rniga `Testing`ni `#if DEBUG` bilan `Feature` ichiga qo'ysangiz grafik soddalashadi, lekin ilovaga kerak bo'lmagan test kodini ham kompilyatsiya qilasiz (TMA hujjatidagi ogohlantirish).

**Qachon TMA kerak emas (yoki yengilroq shakli yetarli)?** Agar ilova kichik bo'lsa (bitta ekran, bir nechta feature, bitta dasturchi), 5 target × har feature ortiqcha murakkablik bo'lishi mumkin. Bunday holda Interface'siz oddiy modullar (shunchaki `Feature` + `FeatureTests`) ham yetarli — chegarani keyinroq, og'riq paydo bo'lganda qo'shasiz. TMA strukturasi "hammasi yoki hech narsa" emas: avval modullarga bo'ling, Interface ajratishni build vaqti yoki coupling muammoga aylanganda kiriting. Agar sizda allaqachon Xcode (yoki XcodeGen/SPM) loyihasi bor bo'lsa va uni shu strukturaga ko'chirmoqchi bo'lsangiz, [05. Migratsiya](/ios-lessons/tuist-05-migration) darsidagi bosqichma-bosqich yondashuvdan foydalaning.

> **⚠️ Diqqat:** Yuqoridagi barcha kompaniya raqamlari **vendor yoki mijoz muallifligidagi** case study'lardan. Bazel, Tuist va oddiy Xcode'ni bir xil kod bazasida solishtiradigan mustaqil, neytral benchmark 2025–2026 da topilmadi. Har bir raqam bitta tool'ni o'sha kompaniyaning avvalgi holati bilan solishtiradi, boshqa tool bilan emas.

---

## 14. Amaliy mashqlar {#mashqlar}

1. **Yangi feature qo'shing.** `Profile` nomli 4-modul yarating (`Module.feature(name: "Profile", ...)`). U `DesignSystemInterface`ga bog'lansin. `ProfileInterface`da `ProfileServing` protokoli va `User` modeli bo'lsin. `tuist generate` qiling va build qiling.

2. **Interface chegarasini sinang.** `Home/Sources`da `import Catalog` (implementatsiya!) deb yozib ko'ring va `HomeViewModel`da `CatalogService()`ni to'g'ridan-to'g'ri yarating. `tuist inspect dependencies --only implicit` ni ishga tushiring — u nima deydi? Keyin to'g'ri yo'lga (Interface + DI) qaytaring.

3. **O'lchovni takrorlang.** `Profile`ga `Home` faqat Interface orqali bog'lansin. `Profile/Sources`dagi bir faylni o'zgartiring va `xcodebuild` bilan qaysi target qayta kompilyatsiya bo'lishini tekshiring. Keyin `ProfileInterface`ni o'zgartiring va farqni ko'ring. Natijani jadvalga yozing.

4. **Statik/dinamik almashtirish.** ⭐ Factory'ga `productType()` funksiyasini qo'shing (8-bo'lim). `tuist generate` va `TUIST_PRODUCT_TYPE=static-library tuist generate` orasidagi farqni `tuist graph` yoki build sozlamalarida tekshiring.

5. **Example app'ni ishga tushiring.** `HomeExample`ni simulyatorda oching (Xcode'da `Home` scheme → Run, yoki `tuist generate Home` bilan fokuslang). U `CatalogService` + `DefaultTheme` bilan ishlayotganini ko'ring. Keyin uni `StubCatalogService` + `MockTheme`ga o'zgartiring — server kerak emasligini his qiling.

6. **CI tekshiruvi yozing.** ⭐ GitHub Actions (yoki boshqa CI) qadamida `tuist inspect dependencies` ni qo'shing, shunda har bir PR implicit/redundant bog'liqlik kiritsa, build yiqiladi. Toss aynan shunday "cacheable ulush" tekshiruvini qo'ygan.

---

## 15. O'zingizni tekshiring {#checklist}

- [ ] µFeature / TMA'da bitta modul nechta target'dan iborat va har biri nima qiladi — ayta olaman.
- [ ] "Source'ga emas, Interface'ga bog'lan" qoidasini va u clean build'ni nega tezlatishini (`.swiftmodule` mexanizmi) tushunaman.
- [ ] `ProjectDescriptionHelpers`da `Module.feature(...)` factory yozib, undan `Project.swift`da foydalana olaman.
- [ ] `FeatureTesting` target nega alohida va uni boshqa modul testi qanday qayta ishlatishini bilaman.
- [ ] Dependency injection'ni composition root (App)da yig'a olaman; nega faqat App implementatsiya modullariga bog'lanishini tushunaman.
- [ ] Development uchun dinamik, release uchun statik product type — va `Environment` orqali almashtirishni bilaman.
- [ ] `tuist inspect dependencies --only implicit` nima ushlashini va nega CI'da kerakligini tushunaman.
- [ ] Interface o'zgarishi va implementatsiya o'zgarishi qaysi target'larni qayta kompilyatsiya qilishini bashorat qila olaman.

---

## 16. Xulosa {#xulosa}

- **TMA (eski nomi µFeatures)** — ilovani mustaqil feature modullaridan, aniq Interface'lar orqali quradi. Maqsad: har bir feature'ni tez **build, test, try** qilish.
- Bitta modul = **5 target**: Interface (shartnoma), Source (implementatsiya), Testing (qayta ishlatiladigan mock), Tests, Example (mustaqil mini-app).
- **Asosiy qoida:** modul boshqa modulning **Interface**'iga bog'lanadi, Source'iga emas. Buni biz o'lchadik: Catalog implementatsiyasini o'zgartirganda faqat `Catalog` qayta kompilyatsiya bo'ldi (2.3 s), `Home` tegilmadi; Interface'ni o'zgartirganda butun zanjir qayta qurildi (4 target, 3.1 s).
- `ProjectDescriptionHelpers`dagi **factory** 5 target'ni avtomatlashtiradi — `Project.swift` qisqa va konvensiya bir xil qoladi.
- Faqat **App (composition root)** implementatsiyalarni yig'adi va inject qiladi; qolgan modullar protokollarni biladi.
- Development uchun **dinamik**, release uchun **statik** framework (`Environment` orqali almashtiriladi). Mergeable libraries tavsiya etilmaydi.
- `tuist inspect dependencies` TMA chegarasini majburlaydi — implicit (yashirin) va redundant (ortiqcha) bog'liqliklarni ushlaydi. Men bu loyihada aynan shu bilan implicit bog'liqliklarni topdim va tuzatdim.
- Miqyos: Monzo (~200 modul, P50 PR 52→15 daq), Toss (~1000 modul), monday.com (~200 modul) — hammasi aynan shu ajratishga tayanadi (vendor case study'lar).

> **Keyingi dars:** [07. Cache va optimizatsiya — binary cache, selective testing, Xcode cache va build insights](/ios-lessons/tuist-07-cache-optimizatsiya) — bu darsda qurgan modullarni endi **binary cache** qilib, qayta build'ni butunlay o'tkazib yuborishni va faqat o'zgargan modullar testini ishga tushirishni o'rganamiz.

---

## 17. Manbalar {#manbalar}

1. [The Modular Architecture (TMA) — Tuist docs](https://tuist.dev/en/docs/guides/features/projects/tma-architecture) — 5 target, core principle, dependencies, product types.
2. [Code sharing — Tuist docs](https://tuist.dev/en/docs/guides/features/projects/code-sharing) — ProjectDescriptionHelpers va `featureFramework` factory namunasi.
3. [Synthesized files — Tuist docs](https://tuist.dev/en/docs/guides/features/projects/synthesized-files) — bundle accessor'lar (`Bundle.module`), resource synthesizer'lar.
4. [Dependencies — Tuist docs](https://tuist.dev/en/docs/guides/features/projects/dependencies) — product type, `PackageSettings`, static/dynamic.
5. [Reducing iOS Build Times by using Interface Modules — SwiftRock](https://swiftrocks.com/reducing-ios-build-times-by-using-interface-targets) — Interface ajratish g'oyasining asli (TMA hujjati shunga tayanadi).
6. [Scaling a mature iOS codebase with Tuist — Asana Engineering (2023-02-21)](https://asana.com/inside-asana/scaling-a-mature-ios-codebase-with-tuist) — ~10 muhandis, ~300k LOC, 45 s → 15 s median, Inbox 30–50 s → 4 s, kamchiliklar.
7. [Monzo case study — Tuist](https://github.com/tuist/tuist/blob/main/server/priv/marketing/case_studies/monzo.md) — ~50→200 modul, P50 PR 52→15 daq, module cache warming.
8. [Toss: Building less to ship faster — Tuist case study](https://github.com/tuist/tuist/blob/main/server/priv/marketing/case_studies/toss.md) va [Tuist "scale" blog](https://tuist.dev/blog/2025/09/22/scale) — ~1000 modul, graph computation narxi, focus workflow, implicit import tozalash.
9. [monday.com](https://github.com/tuist/tuist/blob/main/server/priv/marketing/case_studies/monday.md) va [Delivery Hero](https://github.com/tuist/tuist/blob/main/server/priv/marketing/case_studies/delivery-hero.md) case study'lari — ~200 / ~115 modul, CI yaxshilanishlari.
10. [Trendyol: self-hosted Tuist — Tuist blog (2024-12-16)](https://tuist.dev/blog/2024/12/16/trendyol) — build −65%, UI test tayyorlash 15 daq → 30 s.
11. [Build cache measurement methodology — intrada issue #1886](https://github.com/jonyardley/intrada/issues/1886) — build vaqtini halol o'lchash metodikasi (median of N, load average).
