- Published on
TCA — The Composable Architecture
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
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: View → Action → Reducer → State → View
↓
Effect → Action → ...
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
| Jihat | MVVM | TCA |
|---|---|---|
| State boshqaruvi | Tarqalgan (@Published lar) | Markazlashgan (bitta State struct) |
| Side effects | ViewModel ichida erkin | Effect orqali nazorat ostida |
| Test qilish | ViewModel unit test | TestStore — deterministik |
| O'rganish qiyinligi | Past | Yuqori |
| Boilerplate | Kam | Ko'p |
| Composability | Qo'lda | Framework tomonidan |
| Kichik ilovalar | Mos | Ortiqcha murakkablik |
| Katta ilovalar | Mos | Juda 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.