08. Observer: KVO dan Combine va Swift 6 AsyncStream'gacha
🏛️ iOS Design Patterns — Mukammal Enterprise & Architecture Qo‘llanma
08. Behavioral: Observer Pattern — Reaktiv Dasturlash Asosi
"Agar siz ma'lumot yangilanganini bilish uchun har 1 soniyada taymer bilan tekshirib (Polling) tursangiz — bu batareyani o'ldirishdir. Observer patterni ma'lumot o'zgargandagina barcha qiziquvchilarni avtomatik uyg'otadi (Push Notification modeli)."
1. Observer Pattern Nima va Uning Apple Ekotizimidagi Evolyutsiyasi
Observer (Kuzatuvchi) — bu bitta obyektning (Subject/Publisher) holati o'zgarganda, unga obuna bo'lgan barcha boshqa obyektlarga (Observers/Subscribers) bu haqda avtomatik xabar beruvchi xulq-atvor andozasidir.
flowchart TD
Subject["Subject (Masalan: CartManager / Savat)\nBalans yoki mahsulotlar o'zgardi!"]
Subject -->|"notifyObservers()"| Obs1["Observer 1: CartBadgeView\n(Qizil doiradagi sonni yangilaydi)"]
Subject -->|"notifyObservers()"| Obs2["Observer 2: CheckoutSummaryView\n(Jami summani qayta hisoblaydi)"]
Subject -->|"notifyObservers()"| Obs3["Observer 3: AnalyticsTracker\n('cart_updated' eventini yuboradi)"]
style Subject fill:#131720,stroke:#bef264,stroke-width:2px
style Obs1 fill:#181d26,stroke:#7da2ce,stroke-width:1px
style Obs2 fill:#181d26,stroke:#7da2ce,stroke-width:1px
style Obs3 fill:#181d26,stroke:#7da2ce,stroke-width:1px
Apple Ekotizimida Observerning 4 Avlodi:
- 1-Avlod (Objective-C):
NSNotificationCentervaKVO (Key-Value Observing). String kalitlar xavfli va har safarremoveObserver()chaqirish esdan chiqsa crash bo'lardi. - 2-Avlod (RxSwift): Uchinchi tomon reaktiv kutubxonalari.
- 3-Avlod (iOS 13+):
Combinefreymvorki (@Published,CurrentValueSubject,PassthroughSubject). - 4-Avlod (Swift 5.9+ / Swift 6): Sof
@Observablemakrosi vaAsyncSequence/AsyncStream.
2. Noldan O'zimizning Thread-Safe Observer Tizimimizni Qurish
Keling, hech qanday tashqi freymvorklarsiz sof Swift 6 da xotira oqishidan (Memory Leak) xoli Observer quramiz:
Retain Cycle Xavfi va Zaif Havola (Weak Wrapper):
Agar Subject kuzatuvchilarni oddiy massivda ([Observer]) saqlasa, u ularni kuchli (strong) ushlab turadi. Natijada obunachi ViewController yopilsa ham xotiradan o'chmay qoladi. Yechim: WeakObserverBox konteyneri:
import Foundation
// 1. Kuzatuvchi Protokoli
public protocol CartObserver: AnyObject, Sendable {
func cartDidUpdate(totalItems: Int, totalPrice: Double)
}
// 2. Weak Wrapper (Xotira oqishini oldini oladi)
final class WeakObserverBox: @unchecked Sendable {
weak var value: (any CartObserver)?
init(_ value: any CartObserver) {
self.value = value
}
}
// 3. Markaziy Subject
public final class CartManager: @unchecked Sendable {
public static let shared = CartManager()
private let lock = NSLock()
private var observers: [WeakObserverBox] = []
private(set) var itemsCount: Int = 0
private(set) var totalAmount: Double = 0.0
private init() {}
public func addObserver(_ observer: any CartObserver) {
lock.lock()
defer { lock.unlock() }
// Avval o'lgan (nil bo'lgan) kuzatuvchilarni tozalaymiz
observers.removeAll { $0.value == nil }
observers.append(WeakObserverBox(observer))
}
public func addItem(price: Double) {
lock.lock()
itemsCount += 1
totalAmount += price
let currentCount = itemsCount
let currentTotal = totalAmount
// O'lik havolalarni tozalash
observers.removeAll { $0.value == nil }
let validObservers = observers.compactMap { $0.value }
lock.unlock()
// Barcha obunachilarni xabardor qilish
for observer in validObservers {
observer.cartDidUpdate(totalItems: currentCount, totalPrice: currentTotal)
}
}
}
3. Zamonaviy Swift 6 Standarti: @Observable va AsyncStream
Swift 6 da endi eski delegate yoki Combine o'rniga toza til xususiyatlari qo'llaniladi:
1. SwiftUI uchun @Observable:
import SwiftUI
@Observable
public final class StockTicker {
public var applePrice: Double = 220.50
public var googlePrice: Double = 175.20
public func updatePrices(apple: Double, google: Double) {
self.applePrice = apple
self.googlePrice = google
}
}
SwiftUI ko'rinishida faqat o'sha o'zgaruvchi ishlatilgan joylar avtomatik re-render bo'ladi!
2. Fon Oqimlari uchun AsyncStream:
public final class LocationTracker {
public func locationStream() -> AsyncStream<String> {
AsyncStream { continuation in
// GPS yangilanganda
continuation.yield("41.31, 69.24")
continuation.onTermination = { _ in
// GPS to'xtatildi
}
}
}
}
// Qabul qiluvchi tomonda:
Task {
for await coordinate in tracker.locationStream() {
print("Yangi nuqta: \(coordinate)")
}
}
4. Retain Cycle Qopqoni: [weak self] Nega Majburiy?
Combine yoki qadimiy NotificationCenter ishlatganda quyidagi xatoga yo'l qo'ymang:
// ❌ FOJIALI RETAIN CYCLE:
cancellable = cartService.$total
.sink { newTotal in
self.updateLabel(newTotal) // self kuchli ushlab qolindi!
}
// ✅ TO'G'RI: [weak self]
cancellable = cartService.$total
.sink { [weak self] newTotal in
self?.updateLabel(newTotal)
}
5. Interaktiv Savol-Javob (Self-Check Quiz)
<details> <summary><b>1-Savol: Observer pattern va Pub/Sub (Publish-Subscribe) arxitekturasi o'rtasida qanday nozik farq bor?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim:
- Observer patternida Subject obunachilar ro'yxatini to'g'ridan-to'g'ri o'zi biladi va ularning interfeys metodini chaqiradi.
- Pub/Sub arxitekturasida (masalan,
NotificationCenteryoki Redis Event Bus) Publisher va Subscriber bir-birini mutlaqo bilmaydi! Ularning o'rtasida mustaqil Message Broker / Event Channel turadi.
</details>
<details> <summary><b>2-Savol: Nega Swift 6 da '@Observable' makrosi Combine'dagi 'ObservableObject'dan 10x samaraliroq?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim: Combine'dagi ObservableObject da birorta @Published maydon o'zgarsa, objectWillChange orqali butun View qayta chizilardi (keraksiz renderlar). Yangi @Observable makrosi esa View ichida aynan qaysi bitta xususiyat o'qilgan bo'lsa (Fine-Grained Tracking), faqat o'sha yozuvnigina yangilaydi. </details>
6. Amaliy Topshiriq (Senior Challenge)
UserSessionSubject sinfini yarating:
- Foydalanuvchi tizimga kirganida (
login), chiqqanida (logout) va token yangilanganda barcha ro'yxatdan o'tgan kuzatuvchilarni xabardor qilsin. - Har bir obunachining xotiradan to'g'ri o'chirilishini (
Weak Reference) Unit Test orqali tasdiqlang!