Combine: co to jest, kluczowe pojęcia oraz Publisher i Subscriber

Autor: IT Sectr Opublikowano: 2026-03-17 Czas czytania: 8 min

Combine to framework programowania reaktywnego od Apple, wprowadzony w iOS 13. Według Apple Documentation, 2025, Combine zapewnia jednolity deklaratywny API do przetwarzania zdarzeń asynchronicznych. Publisher określa źródło danych, a Subscriber subskrybuje je.

Najważniejsze

  • Combine — wbudowany framework Apple do programowania reaktywnego, dostępny od iOS 13
  • Publisher — protokół określający źródło wartości Output z możliwością błędu Failure
  • Subscriber — protokół przyjmujący wartości od Publisher z zarządzanym demand
  • Subject — Publisher umożliwiający ręczne wstawianie zdarzeń (PassthroughSubject, CurrentValueSubject)
  • Operators — map, filter, combineLatest, flatMap do transformacji i łączenia strumieni

Czym jest Combine?

Combine to deklaratywny framework Swift do przetwarzania zdarzeń asynchronicznych w czasie. W przeciwieństwie do podejść opartych na callbackach, Combine umożliwia opisywanie potoków przetwarzania danych poprzez łańcuch operatorów. Framework jest zintegrowany z Foundation (URLSession, Timer, NotificationCenter) i SwiftUI (ObservableObject, @Published).

Protokół Publisher

Każdy typ implementujący protokół Publisher musi określić Output (typ wartości) i Failure (typ błędu). Publisher nie jest aktywny do momentu subskrypcji — zaczyna emitować zdarzenia dopiero po wywołaniu subscribe. To zimna semantyka, charakterystyczna dla reaktywnych strumieni, zwiększająca wydajność.

Protokół Subscriber

Subscriber otrzymuje zdarzenia przez trzy metody: receive(subscription:) — potwierdzenie subskrypcji z zarządzaniem demand; receive(_:) — otrzymanie nowej wartości; receive(completion:) — zakończenie strumienia z sukcesem lub błędem. Demand określa liczbę wartości, które Subscriber jest gotów przyjąć — to mechanizm backpressure.

Wbudowane Publisher

Apple dostarcza wiele wbudowanych Publisher: Just emituje jedną wartość i kończy działanie, Future — asynchroniczny wynik z domknięciem, Deferred — opóźnione utworzenie Publisher do momentu subskrypcji. URLSession.dataTaskPublisher przekształca żądanie sieciowe w strumień reaktywny. Timer.publish tworzy okresowy timer. NotificationCenter.default.publisher zamienia powiadomienia w Publisher. Te integracje pozwalają subskrybować zdarzenia bez ręcznego mostkowania.

Publisher i Subscriber w Combine

W Combine Publisher i Subscriber są połączone protokołem Subscription. Subscription to obiekt łączący, który kontroluje przepływ danych. Deweloper nie musi tworzyć własnych typów Publisher — Apple udostępnia wbudowane: Just, Future, Deferred, Fail, Empty, a także Publisher dla URLSession i Timer.

Subject w Combine

Subject to Publisher, do którego można ręcznie wysyłać wartości. PassthroughSubject nie przechowuje stanu: subskrybent otrzymuje tylko zdarzenia, które wystąpiły po subskrypcji. CurrentValueSubject, analogiczny do BehaviorRelay w RxSwift, przechowuje bieżącą wartość i natychmiast przekazuje ją nowemu subskrybentowi.

Backpressure

Mechanizm backpressure w Combine reguluje szybkość przesyłania danych. Subscriber informuje Publisher przez subscription.request(.unlimited) lub subscription.request(.max(N)), ile wartości jest gotów przetworzyć. Zapobiega to przepełnieniu bufora przy nierównomiernej szybkości producenta i konsumenta.

Operatory Combine

Combine zawiera ponad 100 operatorów podzielonych na kategorie: transformacja (map, tryMap, flatMap), filtrowanie (filter, compactMap, removeDuplicates), łączenie (combineLatest, merge, zip), zarządzanie czasem (debounce, throttle, delay) i obsługa błędów (catch, replaceError, retry).

Operator combineLatest łączy ostatnie wartości dwóch Publisher — za każdym razem, gdy którykolwiek z nich emituje nową wartość, domknięcie jest wywoływane z obiema zaktualizowanymi wartościami. Ten operator jest niezastąpiony przy walidacji formularzy, gdzie trzeba śledzić stan wielu pól wejściowych jednocześnie.

Operator debounce opóźnia publikację wartości do czasu, aż upłynie określony interwał bez nowych zdarzeń. Jest to krytyczne dla pól wyszukiwania: żądanie do serwera jest wysyłane dopiero po tym, jak użytkownik przestanie pisać na 300-500 ms, co zmniejsza obciążenie 5-10 razy.

Obsługa błędów

Combine oferuje kilka strategii obsługi błędów na poziomie operatorów. catch przechwytuje Failure i zastępuje Publisher zapasowym — strumień kontynuuje bez awarii. replaceError(with:) podstawia wartość domyślną zamiast błędu. retry(_:) powtarza subskrypcję określoną liczbę razy przy niepowodzeniu. Ważne: typ błędu Never w sygnaturze Publisher gwarantuje, że strumień nigdy nie zakończy się błędem — pozwala to używać Publisher w SwiftUI bez obsługi Failure.

Przykłady kodu Combine

Pierwszy przykład pokazuje podstawowe działanie Just — Publisher, który emituje jedną wartość i kończy działanie:

swift
let publisher = Just("Hello Combine")

publisher
    .sink(receiveCompletion: { completion in
        print("Zakończono: \(completion)")
    }, receiveValue: { value in
        print("Otrzymano: \(value)")
    })

Drugi przykład demonstruje integrację Combine z URLSession dla żądania sieciowego z przetwarzaniem wyniku na głównym wątku:

swift
let url = URL(string: "https://api.example.com")!

URLSession.shared
    .dataTaskPublisher(for: url)
    .map { $0.data }
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("Błąd: \(error)")
        }
    }, receiveValue: { data in
        print("Danych otrzymano: \(data.count)")
    })

Trzeci przykład — użycie CurrentValueSubject do zarządzania stanem:

swift
let counter = CurrentValueSubject<Int, Never>(0)

counter
    .sink { value in
        print("Licznik: \(value)")
    }

counter.send(1) // Wyświetli: Licznik: 1
counter.send(2) // Wyświetli: Licznik: 2
print(counter.value) // Wyświetli: 2

Combine vs RxSwift

Combine i RxSwift rozwiązują to samo zadanie — programowanie reaktywne, ale z różnym podejściem. Combine to natywny framework Apple wbudowany w system: nie wymaga instalacji zależności, jest zintegrowany z SwiftUI i Foundation. RxSwift to biblioteka zewnętrzna z obsługą iOS 9+ i szerszym zestawem operatorów.

Wydajność Combine jest wyższa w typowych scenariuszach dzięki natywnej implementacji w C. RxSwift oferuje więcej narzędzi do debugowania (RxSwift.Resources.total, Debug), ale jego obiektowo zorientowana architektura może być mniej wydajna przy dużych strumieniach danych. Wybór między nimi to kompromis między możliwościami a integracją.

Ważna różnica — model backpressure. W Combine Subscriber zarządza demand przez Subscription, co daje wyraźną kontrolę nad szybkością strumienia. W RxSwift backpressure domyślnie nie jest kontrolowany — Observable emituje wszystkie wartości, a Subscriber przetwarza je w miarę napływania. Dla dużych strumieni danych Combine okazuje się bardziej przewidywalny i bezpieczniejszy pod względem pamięci.

Combine jest głęboko zintegrowany z SwiftUI na poziomie języka. @Published generuje Publisher, ObservableObject udostępnia objectWillChange Publisher. SwiftUI View automatycznie subskrybuje te Publisher i przerysowuje się przy zmianach. Dla UIKit istnieją zewnętrzne nakładki CombineCocoa, ale Combine był pierwotnie projektowany dla SwiftUI, więc w UIKit jego integracja wymaga dodatkowego kodu. RxSwift ma natomiast bogaty ekosystem dla UIKit: RxCocoa dostarcza reaktywne nakładki dla wszystkich elementów UI.

Combine obsługuje integrację z Core Data przez NSFetchedResultsController Publisher. Publisher emituje nowe dane przy każdej zmianie stosu Core Data: dodaniu, usunięciu, aktualizacji rekordów. Pozwala to budować reaktywne listy bez ręcznego wywoływania reloadData. Łącząc Combine z NotificationCenter Publisher dla UIApplication.willResignActiveNotification, deweloper tworzy reaktywną warstwę dla całego cyklu życia aplikacji bez delegatów i callbacków. Dla klas ObservableObject SwiftUI automatycznie aktualizuje View przy zmianie właściwości @Published — Combine zapewnia całe połączenie bez ani jednej linii ręcznego kodu. Testowanie modułowe kodu Combine wykonuje się przez XCTestExpectation i Publisher.sink w przypadkach testowych z izolacją Scheduler przez ImmediateScheduler.

Podczas pracy z Combine w projektach UIKit deweloperzy często używają subskrypcji przez sink i przechowują AnyCancellable w Set. Typowy wzorzec: ViewController tworzy widok, przechowuje cancellables, subskrybuje Publisher z ViewModel. Przy deinit wszystkie subskrypcje są automatycznie anulowane przez kolekcję Cancellables. Do prawidłowej pracy z UIControl w Combine można użyć UIControl.Event Publisher przez rozszerzenie publisher(for:).

Debugowanie Combine odbywa się przez operator print() lub handleEvents(). print(String) loguje wszystkie zdarzenia: receive subscription, request demand, receive value, receive completion. handleEvents daje bardziej precyzyjną kontrolę: można określić domknięcia dla każdego etapu cyklu życia Publisher. Do testowania Combine istnieje CombineExpectations — biblioteka, która pozwala sprawdzać, czy Publisher emituje oczekiwane wartości w testach z wirtualnym czasem i kontrolą demand.

Combine w praktyce

Combine jest aktywnie używany w SwiftUI do zarządzania formularzami: każda właściwość @Published generuje Publisher, SwiftUI subskrybuje go przez View.body. Łącząc wiele Publisher przez combineLatest, walidacja formularza staje się deklaratywna: każdy Publisher śledzi jedno pole, combineLatest zbiera wszystkie wartości, map sprawdza je i zwraca status przycisku Submit. Publishers.Merge łączy wiele Publisher tego samego typu w jeden — przydatne do zbierania zdarzeń z różnych kontrolek w jeden strumień.

Do integracji Combine z UIKit używaj UIControl Publisher przez rozszerzenie: button.publisher(for: .touchUpInside) zwraca Publisher, który emituje zdarzenie przy naciśnięciu. CombineCocoa — biblioteka społecznościowa, dostarczająca Publisher dla wszystkich zdarzeń UIControl, UITextView.text, UIScrollView.contentOffset. Przy rezygnacji z subskrypcji cancellables Store jest czyszczony w deinit, gwarantując, że żadna subskrypcja nie przeżyje swojego właściciela. Dla UIControl z wieloma stanami używaj Publishers.MergeMany do łączenia Publisher w tablicę.

Często zadawane pytania

Od której wersji iOS jest dostępny Combine?

Combine jest dostępny od iOS 13, macOS 10.15, tvOS 13 i watchOS 6. Ogranicza to jego użycie w projektach wymagających wsparcia starszych wersji. Dla iOS 12 i starszych stosuje się RxSwift lub biblioteki zewnętrzne.

Czym różni się PassthroughSubject od CurrentValueSubject?

PassthroughSubject nie przechowuje stanu i nie powtarza ostatniej wartości nowym subskrybentom — otrzymują oni tylko przyszłe zdarzenia. CurrentValueSubject przechowuje bieżącą wartość i natychmiast przekazuje ją nowemu subskrybentowi. CurrentValueSubject.value umożliwia odczyt i zapis bieżącej wartości synchronicznie.

Jak Combine integruje się ze SwiftUI?

Combine leży u podstaw SwiftUI: @Published generuje Publisher, ObservableObject używa objectWillChange Publisher. SwiftUI View automatycznie subskrybuje @ObservedObject i @StateObject przez potok Combine, przerysowując View przy zmianie właściwości.

Czym jest backpressure w Combine?

Backpressure to mechanizm zarządzania szybkością przesyłania danych. Subscriber informuje Subscription, ile wartości jest gotów przetworzyć (demand). Publisher nie może emitować więcej niż zażądano. Jest to krytyczne przy pracy z dużymi strumieniami danych z gniazd sieciowych.

Jak obsługiwać błędy w Combine?

Operator catch przechwytuje błąd i zastępuje Publisher fallbackiem. replaceError zastępuje błąd wartością domyślną. retry powtarza subskrypcję określoną liczbę razy przy błędzie. Wszystkie operatory należy umieszczać przed subscribe, aby wykluczyć awarię przy Failure.

Podsumowanie

  • Combine — wbudowany framework Apple do programowania reaktywnego od iOS 13
  • Publisher — protokół z Output i Failure, zaczyna działanie po subskrypcji
  • Subscriber — przyjmuje wartości przez receive z obsługą demand
  • Subject (PassthroughSubject, CurrentValueSubject) — Publisher z ręcznym wstawianiem zdarzeń
  • Operatory map, filter, combineLatest, debounce, catch — podstawa potoku
  • Backpressure przez demand zapobiega przepełnieniu bufora Subscriber
  • Wybieraj Combine dla nowych projektów od iOS 13, RxSwift — dla wsparcia starszych wersji

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również