07. TCA (The Composable Architecture) — Point-Free
Arxitektura
iOS Arxitektura 7: TCA (The Composable Architecture) — Point-Free'ning Zamonaviy Yondashuvi
Holat: 2026-yil oktabr. TCA bugungi kunda SwiftUI ilovalari uchun eng kuchli va keng tarqalgan state management va arxitektura yechimlaridan biriga aylangan.
Mundarija
- TCA nima?
- TCA tarkibiy qismlari
- Data Flow (Ma'lumotlar oqimi)
- Amaliy loyiha: Counter
- Effect'lar bilan ishlash (API Call)
- Composition (Kompozitsiya)
- Navigation TCA'da
- Testing TCA
- TCA qachon ishlatiladi va uning kamchiliklari
- Xulosa
- Manbalar
1. TCA nima? {#tca-nima}
The Composable Architecture (TCA) — Point-Free (Brandon Williams va Stephen Celis) tomonidan yaratilgan bo'lib, SwiftUI va Swift ilovalari uchun maxsus ishlab chiqilgan arxitektura kutubxonasidir.
- Asosiy g'oya: Unidirectional data flow (bir yo'nalishli ma'lumot oqimi).
- Tarqalishi: GitHub'da juda mashhur (12,000+ yulduzlar) va ko'plab yirik production ilovalarda ishlatiladi.
- Maqsad: State boshqaruvini barqarorlashtirish, kompozitsiya orqali modullilikni oshirish va test qilishni mutlaqo deterministik qilish.
2. TCA tarkibiy qismlari {#tarkibiy-qismlar}
TCA asosan bir nechta fundamental tushunchalardan iborat:
- State: Ekranning yoki modulning joriy holati. Odatda
structko'rinishida yoziladi va o'zgaruvchilarni saqlaydi. - Action: Foydalanuvchi harakatlari (masalan, tugma bosilishi) va tizim hodisalari (masalan, API javobi kelishi). Odatda
enum. - Reducer: State va Action ni qabul qilib, yangi State ni qaytaruvchi sof funksiya.
- Store: State ni o'zida saqlaydi va Action'larni qabul qilib Reducer orqali State ni yangilaydi.
- Effect: Asinxron operatsiyalar (API chaqiruvlari, taymerlar va hk). Ular oxir-oqibat yana Action qaytaradi.
- Dependencies (yangi Environment): Tashqi bog'lanishlar va servislar.
3. Data Flow (Ma'lumotlar oqimi) {#data-flow}
TCA'da ma'lumotlar oqimi bir yo'nalishli va doiraviy:
The Composable Architecture (TCA) Data Flow
4. Amaliy loyiha: Counter {#amaliy-loyiha}
Eng oddiy TCA namunasi - bu Counter. @Reducer va @ObservableState makrolari (Swift 5.9+ / TCA modern) orqali kodingiz ancha qisqaradi.
import SwiftUI
import ComposableArchitecture
@Reducer
struct CounterFeature {
// 1. State
@ObservableState
struct State: Equatable {
var count = 0
}
// 2. Action
enum Action {
case incrementButtonTapped
case decrementButtonTapped
}
// 3. Reducer
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .incrementButtonTapped:
state.count += 1
return .none // Effect qaytarmaydi
case .decrementButtonTapped:
state.count -= 1
return .none
}
}
}
}
// 4. View
struct CounterView: View {
// Store obyekti View'ga beriladi
let store: StoreOf<CounterFeature>
var body: some View {
VStack(spacing: 20) {
Text("\(store.count)")
.font(.largeTitle)
HStack {
Button("-") {
store.send(.decrementButtonTapped)
}
.font(.largeTitle)
Button("+") {
store.send(.incrementButtonTapped)
}
.font(.largeTitle)
}
}
.padding()
}
}
5. Effect'lar bilan ishlash (API Call) {#effects}
Asinxron ishlarni bajarish (API chaqiruvlari, bazadan o'qish) uchun Effect ishlatiladi. Effect.run yoki qisqacha .run orqali asinxron blok yoziladi va u yana yangi Action qaytaradi.
TCA Side Effects va Dependencies Boshqaruvi
import SwiftUI
import ComposableArchitecture
@Reducer
struct UserListFeature {
@ObservableState
struct State: Equatable {
var users: [String] = []
var isLoading = false
}
enum Action {
case fetchUsersTapped
case usersResponse([String])
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .fetchUsersTapped:
state.isLoading = true
return .run { send in
// 1. Asinxron operatsiya (masalan, API call)
try await Task.sleep(for: .seconds(2))
let fetchedUsers = ["Ali", "Vali", "Hasan"]
// 2. Natijani yuborish
await send(.usersResponse(fetchedUsers))
}
case let .usersResponse(users):
state.isLoading = false
state.users = users
return .none
}
}
}
}
struct UserListView: View {
let store: StoreOf<UserListFeature>
var body: some View {
List {
if store.isLoading {
ProgressView()
} else {
ForEach(store.users, id: \.self) { user in
Text(user)
}
}
Button("Users Olib Kelish") {
store.send(.fetchUsersTapped)
}
}
}
}
Senior eslatmasi: Har doim API call natijalarini to'g'ridan-to'g'ri Effect ichida o'zgartirmang! Effect ichidan
.send()orqali Action qaytaring, State faqat Reducer'ning o'zida sinxron o'zgarishi kerak.
6. Composition (Kompozitsiya) {#composition}
TCA ning eng kuchli xususiyati — bu composition. Katta ilova bir qancha kichik va mustaqil Feature'larga (Reducer'larga) bo'linadi va ular parent tomonidan birlashtiriladi.
import SwiftUI
import ComposableArchitecture
@Reducer
struct AppFeature {
@ObservableState
struct State: Equatable {
var counter = CounterFeature.State()
var userList = UserListFeature.State()
}
enum Action {
case counter(CounterFeature.Action)
case userList(UserListFeature.Action)
}
var body: some ReducerOf<Self> {
Scope(state: \.counter, action: \.action.counter) {
CounterFeature()
}
Scope(state: \.userList, action: \.action.userList) {
UserListFeature()
}
Reduce { state, action in
// Parent leveldagi o'zgarishlar (agar kerak bo'lsa)
return .none
}
}
}
7. Navigation TCA'da {#navigation}
TCA'da navigatsiya ham State tomonidan boshqariladi (State-driven navigation). Yangi makrolar yordamida bu juda osonlashdi.
@Reducer
struct ContactsFeature {
@ObservableState
struct State: Equatable {
// Modal uchun
@Presents var addContact: ContactDetailFeature.State?
var contacts: [String] = []
}
enum Action {
case addButtonTapped
// Modal actionlari uchun
case addContact(PresentationAction<ContactDetailFeature.Action>)
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .addButtonTapped:
// state.addContact = ContactDetailFeature.State(name: "")
return .none
case let .addContact(.presented(.saveTapped(name))):
state.contacts.append(name)
state.addContact = nil // modalni yopish
return .none
case .addContact:
return .none
}
}
.ifLet(\.$addContact, action: \.addContact) {
// ContactDetailFeature()
}
}
}
// View-da .sheet modifier misoli:
/*
.sheet(item: $store.scope(state: \.addContact, action: \.addContact)) { store in
// ContactDetailView(store: store)
}
*/
8. Testing TCA {#testing}
TCA da test yozish shu qadar oson va ishonchli-ki, bu uning eng katta plyusi hisoblanadi. Siz TestStore ishlatasiz va action yuborib, state qanday o'zgarganini tasdiqlaysiz.
import XCTest
import ComposableArchitecture
@MainActor
final class CounterTests: XCTestCase {
func testIncrement() async {
let store = TestStore(
initialState: CounterFeature.State(count: 0)
) {
CounterFeature()
}
await store.send(.incrementButtonTapped) {
$0.count = 1
}
}
func testDecrement() async {
let store = TestStore(
initialState: CounterFeature.State(count: 1)
) {
CounterFeature()
}
await store.send(.decrementButtonTapped) {
$0.count = 0
}
}
}
Muhim: TCA testlarida siz yuborilayotgan barcha Action'lar va State o'zgarishlarini 100% ketma-ketlikda yozishingiz shart. Aks holda test fail bo'ladi. Bu tizimda yashirin (hidden) o'zgarishlar bo'lishini oldini oladi.
9. TCA qachon ishlatiladi va uning kamchiliklari {#qachon-ishlatish}
Qachon ishlatiladi?
- Murakkab state boshqaruvi kerak bo'lgan yirik ilovalar.
- Test coverage eng muhim masala bo'lganida.
- SwiftUI-first loyihalar (ayniqsa iOS 16+ va undan yuqori uchun).
- Predictable data flow kerak bo'lgan katta jamoalarda.
Kamchiliklari:
- Learning curve (O'rganish qiyinligi): Juda ko'p yangi tushunchalar mavjud (Reducer, Effect, Dependencies).
- Boilerplate code: Har bir kichik element uchun State, Action, Reducer yaratish ko'p vaqt va qator kod talab qiladi.
- UIKit integratsiyasi: UIKit bilan ham ishlatsa bo'ladi, lekin uning to'liq salohiyati SwiftUI bilan namoyon bo'ladi.
- Performance: Katta struct (State) qayta yozilaverganligi sababli barcha view'lar render bo'lib ketishi mumkin (albatta, to'g'ri
Scopeva@ObservableStateishlatilsa buning oldini olish mumkin).
10. Xulosa {#xulosa}
TCA shunchaki Redux'ning SwiftUI nusxasi emas, balki asinxron operatsiyalar, dependencies inyeksiyasi va testlashni o'zida birlashtirgan to'liq va kuchli arxitekturadir. Yangi @Reducer va @ObservableState makrolari tufayli boilerplate kod sezilarli darajada kamaydi, bu esa uni endilikda nafaqat katta korporativ ilovalar, balki o'rtacha o'lchamdagi loyihalar uchun ham ajoyib tanlovga aylantiradi.