Published on

Combine-da Publisher va Subscriber-lar — SwiftUI loyihasida amaliy qo'llash

Authors

Avvalgi videolarda Timer va onReceive-ni View ichida ishlatdik. Bu videoda esa bir qadam oldinga o'tamiz: barcha logika ViewModel-ga ko'chiriladi, va @Published o'zgaruvchilarni o'zimiz subscribe qilamiz.


@Published = Publisher

Bu muhim tushuncha: ViewModel ichidagi har bir @Published o'zgaruvchi — bu publisher. Har yangilanishda u qiymat chiqaradi. Bu qiymatni sink bilan kuzatib, lozim amallarni bajarish mumkin.

class SubscriberViewModel: ObservableObject {

    @Published var count: Int = 0
    @Published var textFieldText: String = ""
    @Published var textIsValid: Bool = false
    @Published var showButton: Bool = false

    var cancellables = Set<AnyCancellable>()

    init() {
        setUpTimer()
        addTextFieldSubscriber()
        addButtonSubscriber()
    }
}

1. Timer — ViewModel ichida

Avvalgi videoda timer View-da edi. Endi ViewModel-da:

func setUpTimer() {
    Timer.publish(every: 1, on: .main, in: .common)
        .autoconnect()
        .sink { [weak self] _ in
            guard let self = self else { return }
            // self endi optional emas — xavfsiz foydalanish mumkin

            // Timer 10 ga yetganda to'xtatish
            if self.count >= 10 {
                self.timer?.cancel()
            } else {
                self.count += 1
            }
        }
        .store(in: &cancellables)
}

View-da onReceive vs ViewModel-da sink

// VIEW-DA — onReceive (avvalgi video)
.onReceive(timer) { value in
    currentDate = value
}

// VIEWMODEL-DA — sink
timer
    .sink { [weak self] _ in
        self?.count += 1
    }
    .store(in: &cancellables)

onReceive — faqat View-da ishlaydi. ViewModel-da esa sink ishlatiladi.


2. guard let self = self — professional yondashuv

[weak self] ishlatilganda self optional bo'ladi. Har safar self?.count, self?.timer deb yozish o'rniga — bir marta tekshirib, kuchli reference olish:

.sink { [weak self] _ in
    // Bu usul — har doim self?. yozmasdan
    guard let self = self else { return }

    // Endi self optional emas
    self.count += 1
    self.timer?.cancel()
}

3. cancellables Set — bir nechta publisher boshqaruvi

Bitta AnyCancellable? o'rniga Set<AnyCancellable> — ko'p publisherlarni birga boshqarish uchun:

var cancellables = Set<AnyCancellable>()

// Har bir publisher-ni shu set-ga saqlash
somePublisher
    .sink { ... }
    .store(in: &cancellables)

// Barchasini bekor qilish
for item in cancellables {
    item.cancel()
}

4. TextField subscriber — matn tekshiruvi

Haqiqiy ilovalarda keng tarqalgan holat: foydalanuvchi yozayotgan matn kamida N ta belgi bo'lsin:

func addTextFieldSubscriber() {
    $textFieldText                        // textFieldText publisher-iga subscribe
        .map { text in
            return text.count > 3         // 3 dan ko'p belgi bo'lsa true
        }
        .sink { [weak self] isValid in
            guard let self = self else { return }
            self.textIsValid = isValid
        }
        .store(in: &cancellables)
}

assign vs sink:

// assign — qisqaroq, lekin weak self yo'q → xotira muammosi
$textFieldText
    .map { $0.count > 3 }
    .assign(to: \.textIsValid, on: self)  // ❌ weak self mumkin emas
    .store(in: &cancellables)

// sink — ko'proq kod, lekin weak self bor → professional
$textFieldText
    .map { $0.count > 3 }
    .sink { [weak self] isValid in        // ✅ weak self — to'g'ri yondashuv
        self?.textIsValid = isValid
    }
    .store(in: &cancellables)

Tavsiya: assign qisqaroq ko'rinsa ham, weak self bo'lmaganligi uchun professional kodda sink afzal.


5. View-da validatsiya ko'rsatish

TextField ustida xmark/checkmark ko'rsatish:

TextField("Biror narsa yozing...", text: $vm.textFieldText)
    .frame(height: 55)
    .padding(.leading)
    .background(Color(.systemGray5))
    .cornerRadius(10)
    .font(.headline)
    .overlay(alignment: .trailing) {
        ZStack {
            // X — matn yetarli emas
            Image(systemName: "xmark")
                .foregroundColor(.red)
                .opacity(
                    vm.textFieldText.count < 1 ? 0 :
                    vm.textIsValid ? 0 : 1
                )

            // Checkmark — matn yetarli
            Image(systemName: "checkmark")
                .foregroundColor(.green)
                .font(.title)
                .opacity(vm.textIsValid ? 1 : 0)
        }
        .padding(.trailing)
    }

6. debounce — qidiruv optimallashtirish

Foydalanuvchi tez yozayotganda har harf uchun logika ishlatmaslik uchun — 0.5 soniya kutib, keyin ishlatish:

func addTextFieldSubscriber() {
    $textFieldText
        .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
        // 0.5 soniyadan keyin oxirgi qiymat bilan ishlaydi
        .map { text in
            return text.count > 3
        }
        .sink { [weak self] isValid in
            guard let self = self else { return }
            self.textIsValid = isValid
        }
        .store(in: &cancellables)
}

Debounce qachon kerak:

  • Qidiruv maydoni — har harf uchun API so'rov jo'natmaslik
  • Murakkab filtrlash — har harf uchun katta hisoblash ishlatmaslik
  • Username tekshiruvi — foydalanuvchi yozishni tugatgunicha kutish

7. combineLatest — ikki publisherni birlashtirish

Ikki publisherning oxirgi qiymatlarini birgalikda kuzatish:

func addButtonSubscriber() {
    $textIsValid
        .combineLatest($count)
        // textIsValid yoki count o'zgarganda — ikkalasining oxirgi qiymat bilan ishla
        .sink { [weak self] isValid, count in
            guard let self = self else { return }

            // Matn to'g'ri VA count 10 dan katta bo'lsa Submit tugmasini ko'rsat
            if isValid && count >= 10 {
                self.showButton = true
            } else {
                self.showButton = false
            }
        }
        .store(in: &cancellables)
}

Submit tugmasi

Button {
    // amal
} label: {
    Text("SUBMIT")
        .font(.headline)
        .foregroundColor(.white)
        .frame(height: 55)
        .frame(maxWidth: .infinity)
        .background(Color.blue)
        .cornerRadius(10)
}
.opacity(vm.showButton ? 1.0 : 0.5)
.disabled(!vm.showButton)

Natija: foydalanuvchi 4+ belgi yozib, timer 10-ga yetgandan keyin Submit tugmasi faollashadi.


To'liq ViewModel

import Combine

class SubscriberViewModel: ObservableObject {

    @Published var count: Int = 0
    @Published var textFieldText: String = ""
    @Published var textIsValid: Bool = false
    @Published var showButton: Bool = false

    var cancellables = Set<AnyCancellable>()

    init() {
        setUpTimer()
        addTextFieldSubscriber()
        addButtonSubscriber()
    }

    func setUpTimer() {
        Timer.publish(every: 1, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] _ in
                guard let self = self else { return }
                self.count += 1
            }
            .store(in: &cancellables)
    }

    func addTextFieldSubscriber() {
        $textFieldText
            .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
            .map { $0.count > 3 }
            .sink { [weak self] isValid in
                self?.textIsValid = isValid
            }
            .store(in: &cancellables)
    }

    func addButtonSubscriber() {
        $textIsValid
            .combineLatest($count)
            .sink { [weak self] isValid, count in
                guard let self = self else { return }
                self.showButton = isValid && count >= 10
            }
            .store(in: &cancellables)
    }
}

Xulosa

VositaMaqsad
@PublishedO'zgaruvchini publisher-ga aylantirish
sinkPublisherga subscribe bo'lish (ViewModel-da)
onReceivePublisherga subscribe bo'lish (View-da)
mapPublisherdan kelgan qiymatni boshqa turga aylantirish
debounceBir qator o'zgarishlarda faqat oxirgini ishlatish
combineLatestIkki publisherning oxirgi qiymatlarini birgalikda olish
assignQiymatni o'zgaruvchiga to'g'ridan-to'g'ri belgilash (weak self yo'q)
store(in:)Publisher-ni cancelables-ga saqlash

Publisher va subscriber yondashuvi — ilova bo'ylab ma'lumotlarni sinxron ushlab turishning eng samarali usuli. @Published o'zgaruvchilar o'zgarsa — barcha kuzatuvchilar avtomatik xabar oladi va ekran yangilanadi.

Buy mea coffee