# iOS Arxitektura 8: MVI va Redux — Unidirectional Data Flow va State Management

> **Holat:** Ushbu dars 2026-yil oktabr oyi holatiga ko'ra zamonaviy iOS (Swift 6, iOS 17+) va SwiftUI texnologiyalariga asoslangan.

## Mundarija
1. [Unidirectional Data Flow (UDF) nima?](#udf-nima)
2. [MVI (Model-View-Intent) Arxitekturasi](#mvi-arxitekturasi)
3. [Redux: Single Source of Truth](#redux)
4. [Amaliy loyiha: To-Do App (MVI Pattern)](#amaliy-loyiha)
5. [Middleware va Side Effects](#middleware-side-effects)
6. [Time Travel Debugging](#time-travel-debugging)
7. [ReSwift: iOS uchun Redux](#reswift)
8. [MVI vs MVVM vs TCA](#taqqoslash)
9. [MVI/Redux qachon ishlatiladi?](#qachon-ishlatiladi)
10. [Xulosa](#xulosa)
11. [Manbalar](#manbalar)

## 1. Unidirectional Data Flow (UDF) nima? {#udf-nima}

**Unidirectional Data Flow (UDF)** — ma'lumotlar ilova bo'ylab faqat BIR YO'NALISHDA oqishini ta'minlaydigan dizayn patterni. Boshqa arxitekturalarda (masalan, MVVM yoki VIPER) ma'lumotlar ikki yo'nalishda (bidirectional) harakatlanishi mumkin, UDF'da esa harakat doim siklik bo'ladi.

![MVI & Redux Unidirectional Data Flow Sikli](/lessons/images/architecture/arch_08_mvi_redux_cycle.svg)

### UDF ning afzalliklari:
- **Predictability (Bashorat qilish qulayligi):** Har bir State o'zgarishi aniq bir Action (Intent) orqali sodir bo'ladi.
- **Debugging:** State o'zgarishlarini kuzatish juda oson. Xato qachon va qaysi Action natijasida yuz berganini bilish mumkin.
- **Testing:** Reducer - bu sof funksiya (pure function). Uni test qilish uchun mock objectlar kerak emas.

## 2. MVI (Model-View-Intent) Arxitekturasi {#mvi-arxitekturasi}

MVI dastlab Android ekotizimida ommalashgan, lekin UDF prinsiplari iOS'da ham juda samarali.

- **Model (State):** UI qanday ko'rinishi kerakligini belgilaydigan ma'lumotlar strukturasi. Bu har doim immutable (o'zgarmas) bo'lishi kerak.
- **View:** State'ni qabul qilib, uni render qiluvchi (ekranga chiqaruvchi) qism.
- **Intent (Action):** Foydalanuvchining harakati (masalan: tugmani bosish, text kiritish) yoki tizim hodisasi (API javobi). Bu View'dan Store'ga boradigan xabar.
- **Reducer:** State va Intent'ni qabul qilib, **Yangi State** qaytaruvchi sof funksiya.
- **Side Effect:** API so'rovlari, ma'lumotlar bazasiga yozish kabi asinxron amallar. Ular Reducer'dan tashqarida bajarilishi kerak.

## 3. Redux: Single Source of Truth {#redux}

Redux asosan JavaScript/React dunyosidan kelgan bo'lib, butun ilova uchun yagona **Store** (single source of truth) ishlatishga asoslangan.

Redux asoslari:
1. **Store:** Ilovaning barcha State'ini o'zida saqlaydi.
2. **Action:** Nima sodir bo'lganini ifodalovchi oddiy obyekt (enum).
3. **Reducer:** Sof funksiya bo'lib, `(CurrentState, Action) -> NewState` tarzida ishlaydi.
4. **Middleware:** Side effect'larni boshqarish uchun. Action Reducer'ga yetib borishidan oldin tutib olinib, asinxron ishlar qilinadi va yangi Action dispatch qilinadi.

![Redux Store, Middleware va Single Source of Truth](/lessons/images/architecture/arch_08_redux_store_middleware.svg)

## 4. Amaliy loyiha: To-Do App (MVI Pattern) {#amaliy-loyiha}

Quyida iOS 17+ (Swift 6 va `@Observable` makrosi) yordamida yaratilgan to'liq ishlaydigan To-Do ilovasi.

```swift
import SwiftUI
import Observation

// MARK: - Model (Domain)
struct Todo: Identifiable, Equatable {
    let id: UUID
    var title: String
    var isCompleted: Bool
    
    init(id: UUID = UUID(), title: String, isCompleted: Bool = false) {
        self.id = id
        self.title = title
        self.isCompleted = isCompleted
    }
}

enum FilterType: Equatable {
    case all, active, completed
}

// MARK: - State (Model in MVI)
struct TodoState: Equatable {
    var todos: [Todo] = []
    var filter: FilterType = .all
    var isLoading: Bool = false
    
    // View uchun hisoblangan property'lar
    var filteredTodos: [Todo] {
        switch filter {
        case .all: return todos
        case .active: return todos.filter { !$0.isCompleted }
        case .completed: return todos.filter { $0.isCompleted }
        }
    }
}

// MARK: - Intent (Action)
enum TodoIntent {
    case addTodo(String)
    case toggleTodo(UUID)
    case deleteTodo(IndexSet)
    case setFilter(FilterType)
    case setLoading(Bool)
}

// MARK: - Reducer
struct TodoReducer {
    // Pure function: State + Intent -> Yangi State
    static func reduce(state: inout TodoState, intent: TodoIntent) {
        switch intent {
        case .addTodo(let title):
            guard !title.isEmpty else { return }
            let newTodo = Todo(title: title)
            state.todos.append(newTodo)
            
        case .toggleTodo(let id):
            if let index = state.todos.firstIndex(where: { $0.id == id }) {
                state.todos[index].isCompleted.toggle()
            }
            
        case .deleteTodo(let indexSet):
            let idsToDelete = indexSet.map { state.filteredTodos[$0].id }
            state.todos.removeAll(where: { idsToDelete.contains($0.id) })
            
        case .setFilter(let filter):
            state.filter = filter
            
        case .setLoading(let isLoading):
            state.isLoading = isLoading
        }
    }
}

// MARK: - Store
@Observable
@MainActor
final class TodoStore {
    private(set) var state: TodoState
    
    init(initialState: TodoState = TodoState()) {
        self.state = initialState
    }
    
    // View'dan Intent qabul qilish
    func dispatch(_ intent: TodoIntent) {
        // Reducer'ga state'ni reference (inout) qilib beramiz
        TodoReducer.reduce(state: &state, intent: intent)
    }
}

// MARK: - View
struct TodoView: View {
    @State private var store = TodoStore()
    @State private var newTodoTitle = ""
    
    var body: some View {
        NavigationView {
            VStack {
                // Filter picker
                Picker("Filter", selection: Binding(
                    get: { store.state.filter },
                    set: { store.dispatch(.setFilter($0)) }
                )) {
                    Text("Barchasi").tag(FilterType.all)
                    Text("Faol").tag(FilterType.active)
                    Text("Tugallangan").tag(FilterType.completed)
                }
                .pickerStyle(SegmentedPickerStyle())
                .padding()
                
                // Add Todo
                HStack {
                    TextField("Yangi vazifa", text: $newTodoTitle)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .onSubmit {
                            store.dispatch(.addTodo(newTodoTitle))
                            newTodoTitle = ""
                        }
                    
                    Button("Qo'shish") {
                        store.dispatch(.addTodo(newTodoTitle))
                        newTodoTitle = ""
                    }
                    .disabled(newTodoTitle.isEmpty)
                }
                .padding(.horizontal)
                
                // Todo List
                List {
                    ForEach(store.state.filteredTodos) { todo in
                        HStack {
                            Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
                                .foregroundColor(todo.isCompleted ? .green : .gray)
                                .onTapGesture {
                                    store.dispatch(.toggleTodo(todo.id))
                                }
                            
                            Text(todo.title)
                                .strikethrough(todo.isCompleted)
                        }
                    }
                    .onDelete { indexSet in
                        store.dispatch(.deleteTodo(indexSet))
                    }
                }
            }
            .navigationTitle("Vazifalar (MVI)")
        }
    }
}

#Preview {
    TodoView()
}
```

> **Senior eslatmasi:** E'tibor bering, View hech qachon `state.todos.append()` qilib state'ni to'g'ridan to'g'ri o'zgartirmaydi. U doim `dispatch(.addTodo(...))` chaqiradi. Ma'lumot faqat bir yo'nalishda oqadi.

## 5. Middleware va Side Effects {#middleware-side-effects}

UDF arxitekturasining eng katta muammosi — API so'rovlari kabi Side Effect'larni qayerda qilish. Reducer'lar mutlaqo sof (pure) bo'lishi kerak. Bu yerda **Middleware** yordamga keladi.

Middleware Action (Intent) ni ushlab qoladi, asinxron operatsiyani bajaradi va natijani yangi Action sifatida Store'ga yuboradi.

```swift
// Middleware Type
typealias Middleware<State, Intent> = (State, Intent, @escaping (Intent) -> Void) -> Void

// Analytics Middleware misoli
func analyticsMiddleware() -> Middleware<TodoState, TodoIntent> {
    return { state, intent, dispatch in
        switch intent {
        case .addTodo(let title):
            print("Analytics: Vazifa qo'shildi - '\(title)'")
        case .deleteTodo:
            print("Analytics: Vazifa o'chirildi")
        default:
            break
        }
    }
}
```

Store'ni o'zgartirib, middleware ishlata oladigan qilishimiz mumkin:

```swift
@Observable
@MainActor
final class StoreWithMiddleware {
    private(set) var state: TodoState
    private let middlewares: [Middleware<TodoState, TodoIntent>]
    
    init(initialState: TodoState = TodoState(), middlewares: [Middleware<TodoState, TodoIntent>] = []) {
        self.state = initialState
        self.middlewares = middlewares
    }
    
    func dispatch(_ intent: TodoIntent) {
        // 1. Reducer orqali State ni yangilash
        TodoReducer.reduce(state: &state, intent: intent)
        
        // 2. Middleware'larni ishga tushirish (yangi dispatch function bilan)
        for middleware in middlewares {
            middleware(state, intent, { [weak self] newIntent in
                // Asinxron kelgan yangi intentlarni yana dispatch qilish
                Task { @MainActor in
                    self?.dispatch(newIntent)
                }
            })
        }
    }
}
```

## 6. Time Travel Debugging {#time-travel-debugging}

MVI va Redux'ning mo'jizalaridan biri — **Time Travel Debugging**. Har bir state o'zgarishi bitta Action bilan sodir bo'lgani va state immutable (valuelar to'plami) bo'lgani uchun, biz oldingi statelarni saqlab qolib, orqaga (undo) qaytishimiz mumkin.

```swift
@Observable
@MainActor
final class HistoryStore {
    private(set) var state: TodoState
    private var pastStates: [TodoState] = []
    
    init(initialState: TodoState = TodoState()) {
        self.state = initialState
    }
    
    func dispatch(_ intent: TodoIntent) {
        // Joriy state'ni tarixga saqlash
        pastStates.append(state)
        
        // Yangilash
        TodoReducer.reduce(state: &state, intent: intent)
    }
    
    func undo() {
        guard let previousState = pastStates.popLast() else { return }
        self.state = previousState
    }
}
```

> **Muhim:** Bu xususiyat chizmachilik, photo editor yoki kiritilgan ma'lumotlarni undo/redo qilish o'ta muhim bo'lgan kompleks ilovalarda bebaho hisoblanadi.

## 7. ReSwift: iOS uchun Redux {#reswift}

Agar siz loyihangizda to'liq Redux pattern'ini ishlatmoqchi bo'lsangiz, `ReSwift` mashhur kutubxona hisoblanadi (garchi SwiftUI+TCA hozir trendda bo'lsa ham, UIKit loyihalarida hali ham ko'p ishlatiladi).

ReSwift arxitekturasi:

```swift
import ReSwift

// 1. State
struct AppState {
    var counter: Int = 0
}

// 2. Action
struct CounterActionIncrease: Action {}
struct CounterActionDecrease: Action {}

// 3. Reducer
func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()
    
    switch action {
    case _ as CounterActionIncrease:
        state.counter += 1
    case _ as CounterActionDecrease:
        state.counter -= 1
    default:
        break
    }
    
    return state
}

// 4. Store yaratish
let mainStore = Store<AppState>(
    reducer: counterReducer,
    state: nil
)

// 5. View (UIKit misolida)
class CounterViewController: UIViewController, StoreSubscriber {
    typealias StoreSubscriberStateType = AppState
    
    let counterLabel = UILabel()
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        mainStore.subscribe(self) // Obuna bo'lish
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        mainStore.unsubscribe(self)
    }
    
    func newState(state: AppState) {
        counterLabel.text = "\(state.counter)"
    }
    
    @objc func increaseTapped() {
        mainStore.dispatch(CounterActionIncrease())
    }
}
```

## 8. MVI vs MVVM vs TCA {#taqqoslash}

| Xususiyat | MVVM | MVI / Redux | TCA (The Composable Architecture) |
| --- | --- | --- | --- |
| **Data Flow** | Bidirectional yoki UDF (ixtiyoriy) | Doim Unidirectional (UDF) | Doim Unidirectional (UDF) |
| **State** | Multiple sources (tarqoq bo'lishi mumkin) | Single Source of Truth (qatiy State struct) | Single Source of Truth (qatiy State struct) |
| **Side Effects** | ViewModel ichida (tartibsiz bo'lishi mumkin) | Middleware orqali | Effect orqali (juda tartibli) |
| **Testing** | Mock objectlar va asinxron testlar kerak | Oson (sof reducer funksiyalarni testlash) | Eng oson (Time-based scheduling o'rnatilgan) |
| **Learning Curve**| Oson | O'rta | Qiyin (katta boilerplate) |
| **Boilerplate** | Kam | O'rta | Ko'p |

## 9. MVI/Redux qachon ishlatiladi? {#qachon-ishlatiladi}

MVI va Redux (yoki umumiy holatda UDF) quyidagi vaziyatlarda eng to'g'ri tanlovdir:
1. **Complex State:** Ekranda ko'plab qismlar bir-biriga bog'liq bo'lsa (masalan: Chat ilovalari, Real-time dashboard'lar).
2. **Undo/Redo talabi:** Har bir foydalanuvchi qadami orqaga qaytarilishi kerak bo'lsa.
3. **Katta jamoalar:** Predictable (bashorat qilinadigan) flow sababli qaysi kod qayerda ishlashini hamma aniq biladi.
4. **Qat'iy bug-tracking:** State'larni log qilib borish orqali crash yuz berganda foydalanuvchi qanday qadamlar qilganini aniq ko'rish mumkin.

## Xulosa {#xulosa}

Unidirectional Data Flow (MVI/Redux) ilovaning holatini (State) boshqarishni juda tartibli va ishonchli qiladi. Swift 6 va `@Observable` bilan bu arxitekturani qurish har qachongidan ham osonlashdi. Garchi oddiy ilovalar uchun MVVM yetarli bo'lsa-da, katta korporativ loyihalarda MVI yoki shunga asoslangan TCA kabi arxitekturalar o'zini to'liq oqlaydi.

## Manbalar {#manbalar}
- [SwiftUI State Management (Apple)](https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app)
- [ReSwift GitHub Repository](https://github.com/ReSwift/ReSwift)
- [Unidirectional Data Flow in Swift](https://swiftwithmajid.com/2019/09/04/modeling-app-state-using-store-in-swiftui/)
- [MVI Architecture for iOS](https://medium.com/@kostiantyn.buhaiov/mvi-architecture-on-ios-3bfa3c675c94)
