# Combine Framework — To'liq Darslik (Zero to Hero)

## Mundarija
1. [Nega Combine kerak? (Motivatsiya)](#nega-combine-kerak)
2. [Combine nima?](#combine-nima)
3. [Asosiy tushunchalar (Core Concepts)](#asosiy-tushunchalar)
4. [Publisher turlari](#publisher-turlari)
5. [Subscriber va Cancellable](#subscriber-va-cancellable)
6. [Operatorlar — Combine'ning kuchi](#operatorlar)
7. [Networking (API bilan ishlash)](#networking)
8. [Combine + SwiftUI integratsiyasi](#combine-swiftui)
9. [Combine + UIKit integratsiyasi](#combine-uikit)
10. [Subject'lar (PassthroughSubject va CurrentValueSubject)](#subjectlar)
11. [Xatolarni boshqarish (Error Handling)](#xatolarni-boshqarish)
12. [Backpressure — Senior darajasi](#backpressure)
13. [Combine vs Async/Await — qachon qaysi biri?](#combine-vs-async-await)
14. [Real loyiha: Qidiruv tizimi (Search)](#real-loyiha-qidiruv)
15. [Xulosa va keyingi qadamlar](#xulosa)

---

## Nega Combine kerak? {#nega-combine-kerak}

### Muammo: Asinxron kod — Callback Hell

Tasavvur qiling: siz foydalanuvchi login qilgandan so'ng, uning profilini yuklashingiz, keyin profildagi ID bo'yicha postlarni yuklashingiz, keyin har bir post'dagi rasmlarni yuklashingiz kerak. **Combine'siz** bu shunday ko'rinadi:

```swift
// ❌ YOMON: Callback Hell (Pyramid of Doom)
func loadEverything() {
    loginUser(email: "test@mail.com", password: "123") { user, error in
        guard let user = user else { return }
        
        fetchProfile(userId: user.id) { profile, error in
            guard let profile = profile else { return }
            
            fetchPosts(profileId: profile.id) { posts, error in
                guard let posts = posts else { return }
                
                fetchImages(for: posts) { images, error in
                    guard let images = images else { return }
                    // Nihoyat! 4 ta ichma-ich (nested) closure
                    // Bu kodni o'qish, testlash va debug qilish — DAHSHAT!
                }
            }
        }
    }
}
```

**Muammolar:**
- 😵 Kodni o'qish juda qiyin (piramida shakli)
- 🐛 Xatolarni boshqarish har bir qatlamda alohida yoziladi
- 💀 `guard` esdan chiqsa — crash
- 🔄 Agar foydalanuvchi login oynaga qaytsa — eski so'rovlar to'xtamaydi (memory leak)
- 🧪 Unit test yozish deyarli imkonsiz

### Yechim: Combine bilan yozilsa

```swift
// ✅ YAXSHI: Combine Pipeline — toza va tushunarli
func loadEverything() {
    loginUser(email: "test@mail.com", password: "123")
        .flatMap { user in fetchProfile(userId: user.id) }
        .flatMap { profile in fetchPosts(profileId: profile.id) }
        .flatMap { posts in fetchImages(for: posts) }
        .receive(on: DispatchQueue.main)
        .sink(
            receiveCompletion: { completion in
                if case .failure(let error) = completion {
                    print("Xatolik: \(error)")
                }
            },
            receiveValue: { images in
                // Tayyor! Barcha rasmlar yuklandi
                self.images = images
            }
        )
        .store(in: &cancellables)
}
```

**Natija:**
- ✅ Kod chiziqli (linear) va o'qish oson
- ✅ Xatoliklar bitta joyda boshqariladi
- ✅ `.store(in:)` orqali xotira avtomatik tozalanadi
- ✅ Unit test yozish oson

### Yana bir motivatsiya: Real-time ma'lumotlar

Ba'zi muammolar **bir martalik** emas, balki **uzluksiz oqim** (stream) sifatida ishlaydi:

| Vaziyat | Callback bilan | Combine bilan |
|---------|:---:|:---:|
| Qidiruv maydoni (har harfda API) | 😫 Murakkab timer + cancel logika | ✅ `.debounce()` — 1 qator |
| Tugmani tez-tez bosish (spam) | 😫 Bool flag + timer | ✅ `.throttle()` — 1 qator |
| 2 ta API natijasini birlashtirish | 😫 DispatchGroup + mutex | ✅ `.combineLatest()` — 1 qator |
| Tarmoq uzilganda qayta urinish | 😫 Rekursiv chaqiruv | ✅ `.retry(3)` — 1 qator |

> 💡 **Xulosa:** Combine — bu **"oddiy ishlash" emas, "professional ishlash"** demak. Agar siz faqat bir martalik API chaqirsangiz, `async/await` ham yetarli. Lekin real loyihalarda stream, debounce, merge, retry kabi ehtiyojlar bo'lganda — Combine hali ham tengsiz.

---

## Combine nima? {#combine-nima}

**Combine** — Apple tomonidan iOS 13 (2019) da taqdim etilgan **Functional Reactive Programming (FRP)** framework'i. U asinxron hodisalarni **"Pipeline" (zanjir)** ko'rinishida yozish imkonini beradi.

### Asosiy g'oya

Hayotiy misol bilan tushuntirish:

```
📡 Publisher (TV stansiya) → 🔧 Operators (Antenna/Filtrlar) → 📺 Subscriber (Televizor)
```

1. **Publisher** — ma'lumot tarqatadi (kanal signal yuboradi)
2. **Operators** — oradagi filtrlar, transformatorlar (signalni tozalaydi, kuchaytiiradi)
3. **Subscriber** — oxirgi natijani qabul qiladi (televizorda ko'rinadi)

### Performance (Ishlash tezligi)

| Framework | Tezlik | Apple native? |
|-----------|--------|:---:|
| **Combine** | ⚡ Eng tez | ✅ Ha |
| RxSwift | 🐢 ~41% sekinroq | ❌ Yo'q |
| Async/Await | ⚡ Tez (1 martalik) | ✅ Ha |

Combine C++ darajasida Apple tizimiga to'g'ridan-to'g'ri integratsiya qilingan, shuning uchun uchinchi tomon kutubxonalaridan sezilarli darajada tezroq ishlaydi.

---

## Asosiy tushunchalar (Core Concepts) {#asosiy-tushunchalar}

Combine **3 ta ustun**ga qurilgan:

### 1. Publisher — Ma'lumot tarqatuvchi

Publisher — bu vaqt o'tishi bilan **qiymatlar** yoki **xatolik** chiqaruvchi tur. U ikkita tur (type) bilan aniqlanadi:
- `Output` — chiqaradigan qiymat turi (masalan, `String`, `Int`, `[Post]`)
- `Failure` — mumkin bo'lgan xatolik turi (masalan, `URLError`, `Never`)

```swift
// Publisher protokoli (tuzilishi)
protocol Publisher {
    associatedtype Output    // Qanday qiymat chiqaradi?
    associatedtype Failure: Error  // Qanday xato bo'lishi mumkin?
}
```

### 2. Subscriber — Obunachi

Subscriber — Publisher'dan kelayotgan ma'lumotlarni **qabul qiluvchi**. Eng ko'p ishlatiladigan subscriber'lar:
- `.sink { }` — qiymatni closure ichida qabul qilish
- `.assign(to:)` — qiymatni to'g'ridan-to'g'ri property'ga bog'lash

### 3. Cancellable — Xotira boshqaruvchisi

Har bir obuna `AnyCancellable` qaytaradi. Bu obunani xotirada ushlab turadi. Agar u yo'q qilinsa — obuna ham to'xtaydi. Bu ARC (Automatic Reference Counting) bilan birgalikda xotira oqishining oldini oladi.

```swift
// To'g'ri ishlatish
var cancellables = Set<AnyCancellable>()

publisher
    .sink { value in print(value) }
    .store(in: &cancellables) // ← xotirani boshqaradi
```

> ⚠️ **Ogohlantirish:** `.store(in:)` chaqirmay qo'ysangiz, obuna darhol o'chadi va siz hech qanday ma'lumot olmaysiz!

---

## Publisher turlari {#publisher-turlari}

### Just — bitta qiymat chiqaradi va tugaydi

```swift
let publisher = Just(42)

publisher.sink { value in
    print(value) // 42
}
```

### `[Array].publisher` — massiv elementlarini birma-bir chiqaradi

```swift
let publisher = [1, 2, 3, 4, 5].publisher

publisher.sink { value in
    print(value)
}
// Natija: 1, 2, 3, 4, 5 (birma-bir)
```

### `@Published` — o'zgaruvchi o'zgarganda avtomatik signal

```swift
class UserViewModel: ObservableObject {
    @Published var username: String = ""
}

let vm = UserViewModel()

vm.$username  // ← $ belgisi publisher'ni oladi
    .sink { newValue in
        print("Yangi nom: \(newValue)")
    }
    .store(in: &cancellables)

vm.username = "Azimjon" // Natija: "Yangi nom: Azimjon"
```

### Timer Publisher — vaqt oralig'ida signal

```swift
Timer.publish(every: 1.0, on: .main, in: .common)
    .autoconnect()
    .sink { date in
        print("Har sekundda: \(date)")
    }
    .store(in: &cancellables)
```

### NotificationCenter Publisher

```swift
NotificationCenter.default
    .publisher(for: UIApplication.didBecomeActiveNotification)
    .sink { _ in
        print("Ilova faol bo'ldi!")
    }
    .store(in: &cancellables)
```

---

## Subscriber va Cancellable {#subscriber-va-cancellable}

### `.sink` — universal subscriber

```swift
let numbers = [10, 20, 30].publisher

numbers
    .sink(
        receiveCompletion: { completion in
            switch completion {
            case .finished:
                print("✅ Stream muvaffaqiyatli tugadi")
            case .failure(let error):
                print("❌ Xatolik: \(error)")
            }
        },
        receiveValue: { value in
            print("Qiymat: \(value)")
        }
    )
    .store(in: &cancellables)
```

### `.assign(to:)` — property'ga to'g'ridan-to'g'ri bog'lash

```swift
class ViewModel: ObservableObject {
    @Published var score: Int = 0
}

let vm = ViewModel()

// score property'sini publisher bilan bog'laymiz
Just(100)
    .assign(to: &vm.$score) // score = 100 bo'ladi

print(vm.score) // 100
```

### Cancellable amaliyoti — obunani bekor qilish

```swift
// Obunani qo'lda bekor qilish
let subscription = [1, 2, 3].publisher
    .sink { print($0) }

subscription.cancel() // Obuna to'xtaydi, xotira tozalanadi
```

---

## Operatorlar — Combine'ning kuchi {#operatorlar}

Operatorlar — bu Publisher va Subscriber o'rtasidagi **filtrlar va transformatorlar**. Ular yordamida ma'lumotni o'zgartirish, filtrlash va birlashtirish mumkin.

### Transformatsiya operatorlari

```swift
// map — har bir elementni o'zgartirish
[1, 2, 3].publisher
    .map { $0 * 10 }   // 10, 20, 30
    .sink { print($0) }
    .store(in: &cancellables)

// compactMap — nil qiymatlarni olib tashlash
["1", "abc", "3"].publisher
    .compactMap { Int($0) }  // 1, 3 (abc o'tkazilmadi)
    .sink { print($0) }
    .store(in: &cancellables)

// flatMap — publisher ichidan yangi publisher chiqarish
[1, 2, 3].publisher
    .flatMap { id in
        fetchUser(id: id) // Har bir ID uchun API chaqiruv
    }
    .sink { user in print(user.name) }
    .store(in: &cancellables)
```

### Filtrlash operatorlari

```swift
// filter — shartga mos kelganlarni o'tkazish
[1, 2, 3, 4, 5, 6].publisher
    .filter { $0 % 2 == 0 }  // 2, 4, 6 (faqat juft sonlar)
    .sink { print($0) }
    .store(in: &cancellables)

// removeDuplicates — ketma-ket takrorlanganlarni olib tashlash
[1, 1, 2, 2, 3, 1].publisher
    .removeDuplicates()  // 1, 2, 3, 1
    .sink { print($0) }
    .store(in: &cancellables)

// first / last — faqat birinchi yoki oxirgi qiymat
[10, 20, 30].publisher
    .first()  // 10
    .sink { print($0) }
    .store(in: &cancellables)
```

### Vaqt bilan ishlash operatorlari

```swift
// debounce — foydalanuvchi yozishni to'xtatguncha kutish
$searchText
    .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
    .sink { text in print("Qidiruv: \(text)") }
    .store(in: &cancellables)

// throttle — ma'lum vaqt oralig'ida faqat bitta signal
buttonTapPublisher
    .throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)
    .sink { print("Tugma bosildi") }
    .store(in: &cancellables)

// delay — signalni kechiktirish
Just("Salom!")
    .delay(for: .seconds(2), scheduler: DispatchQueue.main)
    .sink { print($0) }  // 2 sekunddan keyin "Salom!"
    .store(in: &cancellables)
```

### Birlashtirish operatorlari

```swift
// combineLatest — ikki publisher'dan eng oxirgi qiymatlarni birlashtrish
let name = PassthroughSubject<String, Never>()
let age = PassthroughSubject<Int, Never>()

name.combineLatest(age)
    .sink { name, age in
        print("\(name), \(age) yosh")
    }
    .store(in: &cancellables)

name.send("Ali")
age.send(25)  // "Ali, 25 yosh"
name.send("Vali")  // "Vali, 25 yosh"

// merge — ikki publisher'ni bitta oqimga birlashtirish
let api1 = fetchFromServerA()
let api2 = fetchFromServerB()

api1.merge(with: api2)
    .sink { data in print("Ma'lumot keldi: \(data)") }
    .store(in: &cancellables)

// zip — ikki publisher'dan juftlik bo'lib qiymat olish
let letters = ["A", "B", "C"].publisher
let numbers = [1, 2, 3].publisher

letters.zip(numbers)
    .sink { letter, number in
        print("\(letter)\(number)")  // A1, B2, C3
    }
    .store(in: &cancellables)
```

---

## Networking (API bilan ishlash) {#networking}

### Oddiy API so'rov

```swift
struct Post: Codable, Identifiable {
    let id: Int
    let title: String
    let body: String
}

class PostsViewModel: ObservableObject {
    @Published var posts: [Post] = []
    @Published var isLoading = false
    @Published var errorMessage: String?
    
    var cancellables = Set<AnyCancellable>()
    
    func fetchPosts() {
        isLoading = true
        errorMessage = nil
        
        let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
        
        URLSession.shared.dataTaskPublisher(for: url)
            // 1. Background thread'da JSON'ni parse qilish
            .map(\.data)
            .decode(type: [Post].self, decoder: JSONDecoder())
            // 2. Main thread'ga o'tish (UI yangilash uchun)
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: { [weak self] completion in
                    self?.isLoading = false
                    if case .failure(let error) = completion {
                        self?.errorMessage = error.localizedDescription
                    }
                },
                receiveValue: { [weak self] posts in
                    self?.posts = posts
                }
            )
            .store(in: &cancellables)
    }
}
```

### Ketma-ket API so'rovlar (Chained Requests)

```swift
func loadUserAndPosts(userId: Int) {
    let baseURL = "https://api.example.com"
    
    // 1-qadam: Foydalanuvchini yuklash
    URLSession.shared.dataTaskPublisher(for: URL(string: "\(baseURL)/users/\(userId)")!)
        .map(\.data)
        .decode(type: User.self, decoder: JSONDecoder())
        // 2-qadam: Foydalanuvchining postlarini yuklash
        .flatMap { user in
            URLSession.shared.dataTaskPublisher(for: URL(string: "\(baseURL)/users/\(user.id)/posts")!)
                .map(\.data)
                .decode(type: [Post].self, decoder: JSONDecoder())
        }
        .receive(on: DispatchQueue.main)
        .sink(
            receiveCompletion: { print("Tugadi: \($0)") },
            receiveValue: { posts in
                print("Postlar: \(posts.count) ta")
            }
        )
        .store(in: &cancellables)
}
```

### Bir nechta API'ni parallel chaqirish

```swift
func loadDashboard() {
    let fetchUser = URLSession.shared.dataTaskPublisher(for: userURL)
        .map(\.data)
        .decode(type: User.self, decoder: JSONDecoder())
    
    let fetchPosts = URLSession.shared.dataTaskPublisher(for: postsURL)
        .map(\.data)
        .decode(type: [Post].self, decoder: JSONDecoder())
    
    // Ikkalasini PARALLEL yuklash va natijalarni birlashtirish
    fetchUser.combineLatest(fetchPosts)
        .receive(on: DispatchQueue.main)
        .sink(
            receiveCompletion: { completion in
                if case .failure(let error) = completion {
                    print("Xatolik: \(error)")
                }
            },
            receiveValue: { user, posts in
                self.user = user
                self.posts = posts
            }
        )
        .store(in: &cancellables)
}
```

---

## Combine + SwiftUI integratsiyasi {#combine-swiftui}

### ViewModel Pattern

```swift
class CounterViewModel: ObservableObject {
    @Published var count: Int = 0
    @Published var message: String = ""
    
    var cancellables = Set<AnyCancellable>()
    
    init() {
        // count o'zgarganda avtomatik message yangilanadi
        $count
            .map { value -> String in
                if value == 0 { return "Boshlang!" }
                if value > 10 { return "Ajoyib! 🎉" }
                return "Davom eting: \(value)"
            }
            .assign(to: &$message)
    }
    
    func increment() { count += 1 }
    func decrement() { count -= 1 }
}

// SwiftUI View
struct CounterView: View {
    @StateObject var vm = CounterViewModel()
    
    var body: some View {
        VStack(spacing: 20) {
            Text(vm.message)
                .font(.title)
            
            Text("\(vm.count)")
                .font(.largeTitle.bold())
            
            HStack {
                Button("−") { vm.decrement() }
                Button("+") { vm.increment() }
            }
            .font(.title)
        }
    }
}
```

### Form Validatsiya (Real-world misol)

```swift
class RegistrationViewModel: ObservableObject {
    @Published var email = ""
    @Published var password = ""
    @Published var confirmPassword = ""
    @Published var isValid = false
    @Published var validationMessage = ""
    
    var cancellables = Set<AnyCancellable>()
    
    init() {
        // 3 ta maydonni kuzatib, validatsiyani real-time tekshirish
        Publishers.CombineLatest3($email, $password, $confirmPassword)
            .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
            .map { email, password, confirm -> (Bool, String) in
                // Email tekshirish
                guard email.contains("@") else {
                    return (false, "Email noto'g'ri formatda")
                }
                // Parol uzunligi
                guard password.count >= 8 else {
                    return (false, "Parol kamida 8 ta belgi bo'lishi kerak")
                }
                // Parollar mos kelishi
                guard password == confirm else {
                    return (false, "Parollar mos kelmaydi")
                }
                return (true, "✅ Hammasi to'g'ri!")
            }
            .sink { [weak self] isValid, message in
                self?.isValid = isValid
                self?.validationMessage = message
            }
            .store(in: &cancellables)
    }
}
```

---

## Combine + UIKit integratsiyasi {#combine-uikit}

### UITextField kuzatish

```swift
class SearchViewController: UIViewController {
    let searchField = UITextField()
    var cancellables = Set<AnyCancellable>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // UITextField qiymatini Combine orqali kuzatish
        NotificationCenter.default
            .publisher(for: UITextField.textDidChangeNotification, object: searchField)
            .compactMap { ($0.object as? UITextField)?.text }
            .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
            .removeDuplicates()
            .filter { !$0.isEmpty }
            .sink { [weak self] searchText in
                self?.performSearch(query: searchText)
            }
            .store(in: &cancellables)
    }
    
    func performSearch(query: String) {
        print("Qidiruv: \(query)")
    }
}
```

### UIButton publisher

```swift
// UIControl uchun Combine Extension
extension UIControl {
    func publisher(for event: UIControl.Event) -> AnyPublisher<Void, Never> {
        UIControlPublisher(control: self, event: event)
            .eraseToAnyPublisher()
    }
}

// Ishlatish:
loginButton.publisher(for: .touchUpInside)
    .throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)
    .sink { [weak self] in
        self?.performLogin()
    }
    .store(in: &cancellables)
```

---

## Subject'lar {#subjectlar}

Subject'lar — bu **Publisher** hamda **Subscriber** sifatida ishlaydigan maxsus turlar. Ular orqali dastur kodidan qo'lda qiymat yuborish mumkin.

### PassthroughSubject — qiymatni saqlamaydi

```swift
let subject = PassthroughSubject<String, Never>()

subject
    .sink { value in print("Qabul qilindi: \(value)") }
    .store(in: &cancellables)

subject.send("Salom")     // "Qabul qilindi: Salom"
subject.send("Dunyo")     // "Qabul qilindi: Dunyo"
subject.send(completion: .finished) // Stream tugadi
```

> 💡 **Qachon ishlatiladi?** Tugma bosilganda signal yuborish, event'larni tarqatish.

### CurrentValueSubject — eng oxirgi qiymatni saqlaydi

```swift
let subject = CurrentValueSubject<Int, Never>(0) // Boshlang'ich qiymat: 0

print(subject.value) // 0

subject
    .sink { value in print("Hozirgi: \(value)") }
    .store(in: &cancellables)

subject.send(10)  // "Hozirgi: 10"
subject.send(20)  // "Hozirgi: 20"
print(subject.value) // 20 — har doim oxirgi qiymatni bilish mumkin
```

> 💡 **Qachon ishlatiladi?** State (holat) boshqaruvi: loading holati, score, counter.

### Farqlari jadvalda

| Xususiyat | PassthroughSubject | CurrentValueSubject |
|-----------|:---:|:---:|
| Boshlang'ich qiymat | ❌ Yo'q | ✅ Bor |
| `.value` property | ❌ Yo'q | ✅ Bor |
| Yangi obunachiga oxirgi qiymat | ❌ Bermaydi | ✅ Beradi |
| Ishlatilish joyi | Event/Action | State/Holat |

---

## Xatolarni boshqarish (Error Handling) {#xatolarni-boshqarish}

### `replaceError` — xato bo'lsa default qiymat

```swift
URLSession.shared.dataTaskPublisher(for: url)
    .map(\.data)
    .decode(type: [Post].self, decoder: JSONDecoder())
    .replaceError(with: []) // Xato bo'lsa bo'sh massiv
    .receive(on: DispatchQueue.main)
    .assign(to: &$posts)
```

### `retry` — avtomatik qayta urinish

```swift
URLSession.shared.dataTaskPublisher(for: url)
    .retry(3) // 3 marta qayta urinadi
    .map(\.data)
    .decode(type: [Post].self, decoder: JSONDecoder())
    .catch { error -> Just<[Post]> in
        print("3 urinishdan keyin ham xato: \(error)")
        return Just([])
    }
    .receive(on: DispatchQueue.main)
    .assign(to: &$posts)
```

### `catch` — xatoni ushlash va boshqa publisher'ga o'tish

```swift
primaryAPI()
    .catch { _ in backupAPI() }  // Asosiy xato bosa, zaxira API
    .receive(on: DispatchQueue.main)
    .assign(to: &$data)
```

### `mapError` — xato turini o'zgartirish

```swift
URLSession.shared.dataTaskPublisher(for: url)
    .mapError { urlError -> AppError in
        return AppError.networkFailed(urlError.localizedDescription)
    }
    .sink(
        receiveCompletion: { completion in
            if case .failure(let appError) = completion {
                // Endi AppError turi bilan ishlaysiz
            }
        },
        receiveValue: { _ in }
    )
    .store(in: &cancellables)
```

---

## Backpressure — Senior darajasi {#backpressure}

### Muammo

Tasavvur qiling: 1000 ta rasmni yuklash kerak. Agar hammasi bir vaqtda yuklanishni boshlasa:
- 📈 RAM sakraydi (maqsadsiz 1000 ta URLSession task ochiladi)
- 💥 OOM (Out of Memory) crash
- 🌐 Server ortiqcha so'rovlardan overload bo'ladi

### Yechim: `flatMap(maxPublishers:)`

```swift
func downloadImages(urls: [URL]) {
    urls.publisher
        // Bir vaqtda FAQAT 3 ta rasm yuklanadi
        .flatMap(maxPublishers: .max(3)) { url in
            URLSession.shared.dataTaskPublisher(for: url)
                .map(\.data)
                .replaceError(with: Data())
        }
        .receive(on: DispatchQueue.main)
        .sink { imageData in
            print("Rasm yuklandi: \(imageData.count) bytes")
        }
        .store(in: &cancellables)
}
```

**Natija:** 1000 ta rasm xavfsiz va barqaror yuklanadi — RAM spike yo'q, server overload yo'q.

### Custom Subscriber bilan Demand boshqaruvi

```swift
// Senior-level: Publisher'ga "menga 1tadan ber" deyish
class PaginatedSubscriber: Subscriber {
    typealias Input = [Post]
    typealias Failure = Never
    
    func receive(subscription: Subscription) {
        subscription.request(.max(1)) // Faqat 1 ta sahifa so'rash
    }
    
    func receive(_ input: [Post]) -> Subscribers.Demand {
        print("Sahifa yuklandi: \(input.count) ta post")
        return .max(1) // Yana 1 ta sahifa so'rash
    }
    
    func receive(completion: Subscribers.Completion<Never>) {
        print("Barcha sahifalar yuklandi")
    }
}
```

---

## Combine vs Async/Await — qachon qaysi biri? {#combine-vs-async-await}

| Vaziyat | Combine | Async/Await |
|---------|:---:|:---:|
| Bitta API so'rov | ✅ Mumkin | ✅ **Afzalroq** |
| Ketma-ket 2-3 API | ✅ flatMap | ✅ **Afzalroq** (linear) |
| Real-time stream (chat, timer) | ✅ **Faqat Combine** | ❌ AsyncStream kerak |
| Debounce / Throttle | ✅ **Faqat Combine** | ❌ Qo'lda yozish kerak |
| UI form validatsiya | ✅ **Faqat Combine** | ❌ Murakkab |
| CombineLatest (2+ oqim) | ✅ **Faqat Combine** | ❌ TaskGroup boshqa narsa |
| Retry + Error handling | ✅ 1 qator | ✅ Loop bilan mumkin |

> 💡 **Maslahat:** Ikkalasini **birgalikda** ishlating! Oddiy API chaqiruvlarni `async/await` bilan, murakkab stream/reaktiv logikani Combine bilan yozing.

---

## Real loyiha: Qidiruv tizimi (Search) {#real-loyiha-qidiruv}

Bu — Combine'ning eng kuchli amaliy namunasi. Real ilovadagi qidiruv tizimini yaratamiz:

```swift
class SearchViewModel: ObservableObject {
    @Published var searchText = ""
    @Published var results: [SearchResult] = []
    @Published var isSearching = false
    
    var cancellables = Set<AnyCancellable>()
    
    init() {
        $searchText
            // 1. Foydalanuvchi 0.5 soniya yozishni to'xtatguncha kutish
            .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
            // 2. Oldingi so'z bilan bir xil bo'lsa — o'tkazib yuborish
            .removeDuplicates()
            // 3. Bo'sh matnni filtrlash
            .filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
            // 4. Loading holatini yoqish
            .handleEvents(receiveOutput: { [weak self] _ in
                self?.isSearching = true
            })
            // 5. API so'rov — FAQAT eng oxirgi so'rovni bajarish
            .map { query in
                self.searchAPI(query: query)
                    .replaceError(with: [])
            }
            .switchToLatest() // Eski so'rovlarni bekor qiladi!
            // 6. Main thread'da UI yangilash
            .receive(on: DispatchQueue.main)
            .sink { [weak self] results in
                self?.results = results
                self?.isSearching = false
            }
            .store(in: &cancellables)
    }
    
    private func searchAPI(query: String) -> AnyPublisher<[SearchResult], Error> {
        let url = URL(string: "https://api.example.com/search?q=\(query)")!
        return URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: [SearchResult].self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}
```

**Bu bitta pipeline ichida 6 ta muammoni hal qiladi:**
1. ✅ Server spam bo'lmaydi (`debounce`)
2. ✅ Takroriy so'rov ketmaydi (`removeDuplicates`)
3. ✅ Bo'sh so'rov yuborilmaydi (`filter`)
4. ✅ Loading indikator ko'rinadi (`handleEvents`)
5. ✅ Eski so'rovlar bekor qilinadi (`switchToLatest`)
6. ✅ UI Main thread'da yangilanadi (`receive(on:)`)

> 🔥 Bu kodni callback bilan yozish uchun **60+ qator** kerak bo'lardi. Combine bilan — **20 qator**!

---

## Xulosa va keyingi qadamlar {#xulosa}

### Nimalarni o'rgandik?

| Daraja | Mavzu | Holat |
|--------|-------|:---:|
| 🟢 Beginner | Publisher, Subscriber, Cancellable | ✅ |
| 🟢 Beginner | Just, @Published, Timer publisher | ✅ |
| 🟡 Middle | Operators (map, filter, debounce, combineLatest) | ✅ |
| 🟡 Middle | Networking (URLSession + Combine) | ✅ |
| 🟡 Middle | SwiftUI va UIKit integratsiyasi | ✅ |
| 🔴 Senior | Subject'lar (Passthrough, CurrentValue) | ✅ |
| 🔴 Senior | Error Handling (retry, catch, mapError) | ✅ |
| 🔴 Senior | Backpressure va Demand | ✅ |
| 🔴 Senior | switchToLatest, Custom Subscriber | ✅ |

### Keyingi qadamlar

1. **Amaliyot:** Har bir operatorni Xcode Playground'da sinab ko'ring.
2. **Real loyiha:** Mavjud loyihangizda bitta ViewModel'ni Combine'ga o'tkazing.
3. **Instruments:** Memory Allocations va Time Profiler bilan Combine pipeline'larni tahlil qiling.
4. **WWDC:** "Combine in Practice" (WWDC 2019) videolarni ko'ring.
5. **Testing:** XCTest ichida Combine pipeline'larni test qilishni o'rganing.

> 🎯 **Esda tuting:** Combine — bu shunchaki framework emas, bu **fikrlash tarzi**. Har bir muammoni "oqim" (stream) sifatida ko'rishni o'rganing va siz haqiqiy Senior iOS Engineer bo'lasiz!