Published on

TCA — The Composable Architecture

Authors

TCA (The Composable Architecture) — Point-Free jamoasi yaratgan open-source framework. Redux dan ilhomlangan — barcha state bitta joyda, barcha o'zgarishlar Reducer orqali, barcha tashqi ta'sirlar Effect orqali. Natija: to'liq nazorat qilinadigan va test qilinadigan arxitektura.

TCA asosiy tushunchalari

┌─────────────────────────────────────────────┐
│                                             │
View ──Action──► Reducer ──► State ──► View│
│                      │                      │
│                      ▼                      │
Effect│                      │                      │
│                      ▼                      │
Action│                      │                      │
│                      ▼                      │
Reducer ──► State ──► View│
│                                             │
└─────────────────────────────────────────────┘

Unidirectional: ViewActionReducerStateView
                                 EffectAction...

TCA da sodda misol

import ComposableArchitecture

// ═══════════════════════════════════════════════════════════════
//  🏗 FEATURE — TCA ning asosiy birligi
//
//  @Reducer — TCA macro. Bu struct State + Action + Reducer ni
//  bitta joyda birlashtiradi.
//
//  "Feature" = ilova ning bitta bo'limi (ekran, komponent)
//  Har feature o'z State, Action va Reducer ga ega
//  Katta ilovada feature lar birlashtiriladi (composability)
// ═══════════════════════════════════════════════════════════════
@Reducer
struct SanagichFeature {

    // ── STATE — ilovaning TO'LIQ holati ──
    //
    // @ObservableState — SwiftUI View ni avtomatik yangilash
    // Equatable — avvalgi va yangi state ni SOLISHTIRISH
    //   (agar o'zgarmagan bo'lsa — View qayta chizilmaydi!)
    //
    // State = "bitta haqiqat manbasi" (Single Source of Truth)
    // UI da ko'rinadigan HAMMA narsa shu struct da
    // Hech qanday ma'lumot View yoki boshqa joyda yashirinmaydi
    @ObservableState
    struct State: Equatable {
        var son = 0              // Sanagich qiymati
        var fakt: String?        // API dan kelgan fakt (nil = hali yo'q)
        var yuklanmoqda = false  // Tarmoq so'rovi ketdimi?
    }

    // ── ACTION — nima sodir bo'ldi? ──
    //
    // Enum — barcha MUMKIN BO'LGAN hodisalar ro'yxati
    // Foydalanuvchi amallari:
    //   .oshirTugmasi — "+" tugma bosildi
    //   .kamayirTugmasi — "-" tugma bosildi
    //   .faktSorash — "Fakt olish" tugma bosildi
    //
    // Tizim javoblari (Effect natijasi):
    //   .faktJavob("...") — API dan javob keldi
    //
    // Action = "nima sodir bo'ldi" (PAST tense)
    // "nima qilish kerak" EMAS!
    enum Action {
        case oshirTugmasi       // 👆 Foydalanuvchi + bosdi
        case kamayirTugmasi     // 👆 Foydalanuvchi - bosdi
        case faktSorash         // 👆 Foydalanuvchi fakt so'radi
        case faktJavob(String)  // 📡 API dan javob keldi
    }

    // ── REDUCER — sof logika (state o'zgartiruvchi) ──
    //
    // Reducer = (State, Action) → (State, Effect)
    //
    // QOIDA: Reducer SOF FUNKSIYA bo'lishi kerak!
    // ✅ State ni o'zgartirish
    // ✅ Effect qaytarish (tarmoq, timer kabi)
    // ❌ To'g'ridan-to'g'ri tarmoq so'rovi yo'q
    // ❌ Global o'zgaruvchilar yo'q
    // ❌ Random, Date.now kabi noaniq narsalar yo'q
    //
    // Bu determinism = test qilish JUDA OSON
    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {

            case .oshirTugmasi:
                // State ni o'zgartirish — FAQAT shu yerda mumkin!
                state.son += 1
                state.fakt = nil  // Avvalgi faktni tozalash
                return .none      // Side effect yo'q — faqat state o'zgarishi
                // .none = "hech qanday tashqi ish kerak emas"

            case .kamayirTugmasi:
                state.son -= 1
                state.fakt = nil
                return .none

            case .faktSorash:
                state.yuklanmoqda = true   // Spinner ko'rsatish
                state.fakt = nil           // Eski faktni tozalash

                // ── EFFECT — tashqi dunyo bilan aloqa ──
                //
                // .run — asinxron ish bajarish
                // [son = state.son] — capture: state dan qiymat olish
                //   (state reference type emas — nusxa olish kerak)
                // send — Effect ichidan Action yuborish
                //   (Reducer ga qayta kirish — cycle)
                //
                // Oqim:
                // Action(.faktSorash)
                //   → Reducer: state.yuklanmoqda = true
                //   → Effect: tarmoq so'rovi
                //     → send(.faktJavob("..."))
                //       → Reducer: state.yuklanmoqda = false
                //         → View yangilanadi
                return .run { [son = state.son] send in
                    // Tarmoq so'rovi — bu "side effect"
                    let url = URL(string: "http://numbersapi.com/\(son)")!
                    let (data, _) = try await URLSession.shared.data(from: url)
                    let fakt = String(data: data, encoding: .utf8) ?? "Noma'lum"

                    // Natijani Action sifatida yuborish
                    // Bu Action → Reducer → State → View cycle ni davom ettiradi
                    await send(.faktJavob(fakt))
                }

            case .faktJavob(let fakt):
                // API dan javob keldi — state ni yangilash
                state.yuklanmoqda = false  // Spinner yashirish
                state.fakt = fakt          // Faktni ko'rsatish
                return .none
            }
        }
    }
}


// ═══════════════════════════════════════════════════════════════
//  👁 VIEW — Store dan State O'QIYDI, Action YUBORADI
//
//  View HECH QANDAY logika qilmaydi!
//  View faqat:
//  1. store.son — State dan qiymat O'QIYDI
//  2. store.send(.action) — Action YUBORADI
//
//  Reducer State ni o'zgartiradi → View avtomatik yangilanadi
//  Bu "unidirectional data flow" ning mohiyati
//
//  MVVM dan farq:
//  MVVM: viewModel.qoshish() — metod chaqirish
//  TCA:  store.send(.oshirTugmasi) — xabar yuborish
// ═══════════════════════════════════════════════════════════════
struct SanagichKorinishi: View {
    // StoreOf<Feature> — Feature ning State va Action lari bilan ishlash
    // store = "ombor" — State saqlaydi, Action qabul qiladi
    let store: StoreOf<SanagichFeature>

    var body: some View {
        VStack(spacing: 20) {
            HStack(spacing: 20) {
                // Action yuborish — "-" tugma
                Button("-") { store.send(.kamayirTugmasi) }
                    .font(.title)

                // State dan o'qish — son qiymati
                Text("\(store.son)")
                    .font(.largeTitle.bold())

                // Action yuborish — "+" tugma
                Button("+") { store.send(.oshirTugmasi) }
                    .font(.title)
            }

            // Fakt so'rash tugmasi
            Button("Fakt olish") { store.send(.faktSorash) }
                .disabled(store.yuklanmoqda)
                // disabled — yuklanayotganda tugma o'chiq

            // Yuklanish holati — State dan o'qish
            if store.yuklanmoqda {
                ProgressView()
            }

            // Fakt matni — State dan o'qish (optional)
            if let fakt = store.fakt {
                Text(fakt)
                    .font(.body)
                    .padding()
                    .background(.ultraThinMaterial)
                    .clipShape(RoundedRectangle(cornerRadius: 12))
            }
        }
        .padding()
    }
}


// ═══════════════════════════════════════════════════════════════
//  🚀 APP — Store yaratish va ilovaga uzatish
//
//  Store — ilovaning "bosh ombori"
//  initialState — boshlang'ich holat
//  SanagichFeature() — Reducer (logika)
//
//  Katta ilovada Store ildiz (root) da yaratiladi
//  va child feature larga uzatiladi
// ═══════════════════════════════════════════════════════════════
@main
struct MeningIlovam: App {
    var body: some Scene {
        WindowGroup {
            SanagichKorinishi(
                store: Store(initialState: SanagichFeature.State()) {
                    SanagichFeature()
                    // Bu Reducer — barcha logika shu yerda
                }
            )
        }
    }
}

TCA da test yozish

import ComposableArchitecture
import XCTest

@MainActor
final class SanagichFeatureTests: XCTestCase {
    func test_oshirish() async {
        let store = TestStore(initialState: SanagichFeature.State()) {
            SanagichFeature()
        }

        await store.send(.oshirTugmasi) {
            $0.son = 1      // Kutilgan state o'zgarishi
        }

        await store.send(.oshirTugmasi) {
            $0.son = 2
        }

        await store.send(.kamayirTugmasi) {
            $0.son = 1
        }
    }
}

MVVM vs TCA

JihatMVVMTCA
State boshqaruviTarqalgan (@Published lar)Markazlashgan (bitta State struct)
Side effectsViewModel ichida erkinEffect orqali nazorat ostida
Test qilishViewModel unit testTestStore — deterministik
O'rganish qiyinligiPastYuqori
BoilerplateKamKo'p
ComposabilityQo'ldaFramework tomonidan
Kichik ilovalarMosOrtiqcha murakkablik
Katta ilovalarMosJuda mos

🎯 Topshiriq: TCA ni sinab ko'rish

swift-composable-architecture paketini qo'shing (SPM). Yuqoridagi sanagich misolini yarating. oshirTugmasi va kamayirTugmasi ishlashini tekshiring. TestStore bilan bitta test yozing — send dan keyin state o'zgarishini tasdiqlang.

Buy mea coffee