Combine — klíčové pojmy, Publisher a reaktivní programování

Autor: IT Sectr Publikováno: 2026-05-03 Doba čtení: 11 min

Combine — je nativní reaktivní programovací framework od Apple, představený v iOS 13, macOS Catalina, tvOS 13 a watchOS 6. Poskytuje deklarativní Swift API pro zpracování asynchronních událostí pomocí vzoru Publisher a Subscriber, nahrazující delegáty, closure a NotificationCenter jednotným řetězcem. Podle Apple, 2025 je Combine základem pro SwiftUI a moderní iOS architektury, pracující v těsném spojení s async/await a Structured Concurrency. Framework je navržen pro kompozici asynchronních operací s garancí bezpečnosti vláken.

Hlavní body

  • Combine — nativní reaktivní framework Apple s typy Publisher, Subscriber, Subject a typy chyb přes Failure
  • Publisher vysílá události v čase: hodnoty Output, může skončit úspěchem nebo chybou Failure
  • Subscriber přijímá události z Publisher a může požadovat určitý počet prvků přes Demand
  • Operátory map, flatMap, filter, combineLatest, zip, debounce transformují toky bez závodních stavů
  • SwiftUI používá Combine přes @Published, ObservableObject a @StateObject pro reaktivní aktualizaci rozhraní

Co je Combine?

Combine — je deklarativní reaktivní programovací framework vestavěný do Apple SDK. Implementuje vzor Reactive Streams: Publisher produkuje hodnoty, Subscriber je konzumuje a operátory transformují tok mezi nimi. Combine řeší problém callbacků a delegátů tím, že poskytuje jednotný model kompozice pro jakékoli asynchronní události — od síťových odpovědí po změny UI.

Před příchodem Combine používali iOS vývojáři knihovny třetích stran, především RxSwift. Apple vytvořil Combine jako nativní alternativu s hlubokou integrací do ekosystému: framework podporuje Objective-C přes @objc mosty, pracuje s KVO (Key-Value Observing přes NSObject.keyValuePublisher) a NotificationCenter a je také základem pro SwiftUI. Všechny UIKit komponenty publikované ve SwiftUI používají Combine pod kapotou pro aktualizaci zobrazení.

Combine je navržen s ohledem na Swift Concurrency: od iOS 15 lze Publisher převést na AsyncSequence přes .values a použít v cyklech for-await-in. Zpětná konverze async funkcí na Publisher se provádí přes Future. Podle Apple WWDC 2024 zůstává Combine doporučeným frameworkem pro zpracování streamovaných dat v UIKit aplikacích, navzdory příchodu async/await pro jednotlivá asynchronní volání.

Základní koncepty Combine

Combine je založen na třech protokolech: Publisher (vysílá hodnoty typu Output, může skončit chybou typu Failure), Subscriber (přijímá hodnoty, spravuje Demand — počet požadovaných prvků), Subscription (představuje spojení Publisher-Subscriber s možností zrušení). Kanál přenosu dat je inicializován při subscribe a končí při cancel, completion nebo error. Demand — unikátní koncept Combine: Subscriber informuje Publisher, kolik prvků je připraven zpracovat, implementující backpressure na úrovni protokolu.

Publisher a Subscriber: architektura reaktivního toku

Publisher — protokol se dvěma přidruženými typy: Output (typ vysílaných hodnot) a Failure (typ chyby implementující Error). Pokud tok nemůže skončit chybou, je Failure specifikován jako Never — to zaručuje Subscriberu, že onReceive bude volán pouze s Output. Vestavěné Publisherové zahrnují Just (jedna hodnota), Sequence (pole), URLSession.DataTaskPublisher (síťový požadavek), NotificationCenter.Publisher a @Published property wrapper.

swift
import Combine

// Vytvoření Publisher z posloupnosti
let publisher = [1, 2, 3, 4, 5].publisher

// Vytvoření Subscriber se zpracováním hodnot
class PrintSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never

    func receive(subscription: Subscription) {
        subscription.request(.unlimited)
    }

    func receive(_ input: Int) -> Subscribers.Demand {
        print("Received: \(input)")
        return .unlimited
    }

    func receive(completion: Subscribers.Completion<Never>) {
        print("Dokončeno")
    }
}

publisher.subscribe(PrintSubscriber())

Subscription a Demand

Subscription — je protokol představující aktivní spojení mezi Publisher a Subscriber. Subscriber obdrží Subscription v metodě receive(subscription:) a volá request(_:) pro určení Demand: .unlimited (všechny hodnoty), .max(N) (omezený počet) nebo .none (pauza). Demand se může dynamicky měnit — Subscriber může zvýšit nebo snížit počet požadovaných prvků během přijímání dat. To zajišťuje backpressure bez bufferingu na straně Publisher.

Subject a CurrentValueSubject

Subject — je typ kombinující Publisher a Subscriber. Subject lze použít jako Publisher (lze se na něj přihlásit) a současně jako Subscriber (lze do něj odesílat hodnoty). Combine poskytuje dva druhy Subject: PassthroughSubject (neukládá stav, předává pouze nové hodnoty) a CurrentValueSubject (ukládá aktuální hodnotu a předává ji novým odběratelům). Subject je nezbytný pro integraci imperativního kódu do reaktivních řetězců Combine.

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

// Odběr jako Publisher
let cancellable = subject
    .map { $0.uppercased() }
    .sink { print($0) }

// Odesílání hodnot jako Subscriber
subject.send("hello")  // Tiskne „HELLO"
subject.send("svět")  // Tiskne „WORLD"

CurrentValueSubject se liší od PassthroughSubject přítomností počáteční hodnoty a vlastnosti value: odběratel okamžitě obdrží aktuální hodnotu při přihlášení a poté všechny následné aktualizace. CurrentValueSubject.value je přístupný pro čtení i zápis — změna value automaticky odešle novou hodnotu všem odběratelům. To činí CurrentValueSubject ideální volbou pro reprezentaci stavu v architektuře MVVM: ViewModel publikuje CurrentValueSubject, View se přihlašuje se změnami přes sink.

Oba Subjecty umožňují ukončit tok voláním send(completion: .finished) nebo send(completion: .failure(error)). Po ukončení Subject přestane přijímat a předávat události. Pro dlouhotrvající toky, které by neměly skončit (např. UI události), se doporučuje použít PassthroughSubject s Never Failure, aby se vyloučilo náhodné volání send(completion:).

Operátory transformace a kombinování

Operátory Combine — jsou metody Publisheru vracející nový Publisher. Každý operátor vytváří nový objekt, který se přihlásí k upstream Publisheru a vysílá transformované hodnoty downstream. Protože Publisher je generický typ, operátory zachovávají striktní typování: map transformuje Output<A> na Output<B>, tryMap přidává možnost chyby. Combine obsahuje přibližně 100 vestavěných operátorů.

KategorieOperátorÚčel
Transformacemap / tryMap / flatMapTransformace hodnot nebo toků
Filtrovánífilter / compactMap / removeDuplicatesVýběr nebo čištění hodnot
KombinacecombineLatest / zip / mergeSpojování více Publisherů
Správa časudebounce / throttle / delayZpoždění a zřeďování událostí
Správa chybcatch / retry / replaceErrorObnova po Failure
Správa Demandbuffer / collectSeskupování nebo ukládání do vyrovnávací paměti

flatMap v Combine má důležitý rozdíl oproti verzi RxSwift: přijímá closure vracející Publisher se stejným typem Failure a rozbaluje vnořený Publisher do hlavního toku. flatMap s maxPublishers: .max(1) se chová jako switchMap — ruší předchozí vnořený Publisher při příchodu nové hodnoty. To je kritické pro scénáře vyhledávání: při zadání nového znaku je předchozí HTTP požadavek automaticky zrušen.

swift
// Debounce vyhledávání se zrušením předchozího požadavku
searchTextField.textPublisher
    .debounce(for: .seconds(0.3), scheduler: RunLoop.main)
    .removeDuplicates()
    .flatMap(maxPublishers: .max(1)) { query in
        apiService.searchPublisher(query)
            .catch { _ in Just([]) }
    }
    .receive(on: DispatchQueue.main)
    .sink { results in
        self.tableView.reloadData()
    }
    .store(in: &cancellables)

Kombinační operátory — combineLatest a zip — fungují podobně jako v RxSwift: combineLatest vysílá n-tici posledních hodnot všech Publisherů při změně kteréhokoli z nich; zip kombinuje hodnoty po párech podle indexu. merge spojuje Publisherové stejného typu do jednoho toku, preserveOrder není zaručeno. V Combine je také k dispozici select — vzácný operátor vybírající první dokončený Publisher z několika, a share — multicast toku více odběratelům bez opakovaného provádění.

Schedulery a správa vláken

Scheduler v Combine — je protokol definující kontext provádění pro operátory. Na rozdíl od RxSwift s jeho 5+ vestavěnými Schedulery používá Combine existující mechanismy Apple: DispatchQueue, RunLoop a OperationQueue. Každý z těchto typů odpovídá protokolu Scheduler, což umožňuje jejich přímé předání do receive(on:) a subscribe(on:) bez dalších adaptérů.

receive(on:) přepíná downstream na zadaný Scheduler — ekvivalent observeOn v RxSwift. Všechny operátory po receive(on:) se provádějí na zadaném Scheduleru. subscribe(on:) přepíná upstream — ovlivňuje provádění Publisheru. Typický vzor: subscribe(on: DispatchQueue.global()) pro práci na pozadí a receive(on: DispatchQueue.main) pro aktualizaci UI. Ve SwiftUI při použití .onReceive není vestavěná vazba na hlavní vlákno vyžadována, ale pro sink se doporučuje explicitní receive(on:).main.

swift
// Stahování na pozadí + UI na hlavním vlákně
URLSession.shared.dataTaskPublisher(for: url)
    .subscribe(on: DispatchQueue.global(qos: .background))
    .tryMap { data, response -> Data in
        guard let http = response as? HTTPURLResponse,
              http.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
    .receive(on: DispatchQueue.main)
    .decode(type: User.self, decoder: JSONDecoder())
    .sink(receiveCompletion: { print($0) },
         receiveValue: { self.nameLabel.text = $0.name })
    .store(in: &cancellables)

RunLoop.main — alternativa k DispatchQueue.main pro UI operace. Rozdíl je v tom, že RunLoop.main je vázán na aktuální smyčku událostí aplikace, zatímco DispatchQueue.main je vázán na globální frontu hlavního vlákna. Pro UIKit se doporučuje DispatchQueue.main, pro SwiftUI — RunLoop.main. ImmediateWhenScheduler provádí operace synchronně na aktuálním vlákně — používá se ve výchozím nastavení pro testy a jednoduché Publisherové.

Combine a SwiftUI: integrace přes ObservableObject

ObservableObject — SwiftUI protokol pro objekty publikující změny. Třída implementující ObservableObject může použít property wrapper @Published pro vlastnosti, jejichž změny automaticky informují SwiftUI o potřebě překreslení. Pod kapotou @Published vytváří Publisher, který informuje Publisher objectWillChange při změně wrappedValue. SwiftUI se přihlašuje k objectWillChange přes @StateObject, @ObservedObject nebo @EnvironmentObject.

@Published a @StateObject

@Published — nejběžnější způsob integrace Combine do SwiftUI. Když se změní hodnota vlastnosti @Published, SwiftUI aktualizuje všechny View, které tento objekt používají. @StateObject vytváří instanci ObservableObject a přihlašuje se k jejím změnám. View vytvořená s @StateObject se automaticky překresluje při změně @Published vlastností. Pokud má být objekt předáván mezi více View, používá se @ObservedObject nebo @EnvironmentObject.

swift
class UserViewModel: ObservableObject {
    @Published var name: String = ""
    @Published var age: Int = 0
    private var cancellables = Set<AnyCancellable>()

    init() {
        $name
            .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
            .sink { [weak self] newName in
                AnalyticsService.logNameChange(newName)
            }
            .store(in: &cancellables)
    }
}

struct UserView: View {
    @StateObject var viewModel = UserViewModel()

    var body: some View {
        TextField("Name", text: $viewModel.name)
    }
}

AnyCancellable — typ-mazač pro Cancellable, ukládající token zrušení odběru. Set<AnyCancellable> spravuje životní cyklus odběrů: při deinicializaci vlastníka jsou všechny Cancellable automaticky zrušeny. Ve SwiftUI projektech je Set<AnyCancellable> deklarován v ObservableObject třídě a odběry jsou přidávány přes .store(in: &cancellables). Pro UIKit se používají stejné mechanismy s ukládáním v UIViewController přes &cancellables nebo ručním voláním cancel().

Combine vs RxSwift

Combine a RxSwift řeší stejné úkoly reaktivního programování, ale mají zásadní architektonické rozdíly. Combine — součást Apple SDK se zpětnou kompatibilitou do iOS 13, RxSwift — knihovna třetí strany s podporou iOS 8+. Combine používá striktní typování chyb přes Failure generic, RxSwift — jednotný typ Error. Combine je integrován se SwiftUI na úrovni platformy, RxSwift vyžaduje RxCocoa pro UI rozšíření.

Volba mezi Combine a RxSwift závisí na požadavcích projektu. Pokud je minimální verze iOS >= 13 a projekt používá SwiftUI — Combine je přirozenou volbou díky vestavěné integraci a absenci dalších závislostí. Pokud projekt podporuje iOS 11-12, obsahuje existující RxSwift kódovou základnu nebo vyžaduje specifické operátory dostupné pouze v RxSwift (např. Observable.from(path:)), — RxSwift zůstává oprávněným řešením.

VlastnostCombineRxSwift
VývojářApple (vestavěný v SDK)ReactiveX (komunita)
Verze iOSiOS 13+iOS 8+
Typ chybyGeneric Failure (Never pro UI)Error (jakákoli)
UI integrace@Published + SwiftUIRxCocoa + UIKit
Operátory~100 vestavěných400+ operátorů
Swift ConcurrencyPřes .values (async sequence)Přes mostovou knihovnu

Často kladené otázky

Jaký je rozdíl mezi PassthroughSubject a CurrentValueSubject?

PassthroughSubject neukládá stav — odběratel dostává pouze události odeslané po přihlášení. CurrentValueSubject ukládá aktuální hodnotu a předává ji každému novému odběrateli okamžitě při přihlášení. CurrentValueSubject je vhodný pro reprezentaci stavu (např. isLoggedIn).

Jak zrušit odběr v Combine?

Odběr vrací AnyCancellable, který se ruší při volání cancel() nebo při deinicializaci. Pro skupinovou správu použijte Set<AnyCancellable> — všechny odběry se zruší při vyčištění setu. Toto je obdoba DisposeBag z RxSwift.

Je nutné učit se Combine po async/await?

Ano, Combine zůstává relevantní pro streamovaná data: UI události, debounce, combineLatest, WebSocket. async/await je vhodný pro jednotlivé požadavky, Combine — pro nepřetržité nebo vícenásobné toky. Oba frameworky jsou komplementární — Publisher lze převést na AsyncSequence.

Jak používat Combine s UIKit?

UIKit nemá vestavěné Publisherové, ale Apple poskytuje rozšíření: NotificationCenter.default.publisher(for:), Timer.publish, URLSession.dataTaskPublisher. Pro vlastní UI události se používá PassthroughSubject nebo @IBAction zabalený do Publisher přes Future nebo Subject.

Co je backpressure v Combine?

Backpressure — mechanismus řízení rychlosti toku: Subscriber informuje Publisher přes Demand, kolik prvků je připraven zpracovat. Pokud je Demand = .max(1), Publisher čeká na požadavek před odesláním další hodnoty. To zabraňuje přetečení vyrovnávací paměti při nesouladu rychlostí producenta a konzumenta.

Shrnutí

  • Combine — nativní reaktivní framework Apple pro iOS 13+ se vzorem Publisher-Subscriber a striktním typováním chyb
  • Publisher a Subscriber tvoří komunikační kanál se správou Demand pro backpressure
  • Subject (Passthrough a CurrentValue) integrují imperativní kód do reaktivních řetězců
  • Operátory map, flatMap, combineLatest, debounce, catch zajišťují deklarativní zpracování toků
  • Schedulery přes DispatchQueue a RunLoop spravují vlákna provádění bez blokování UI
  • SwiftUI používá Combine přes @Published, ObservableObject a @StateObject pro reaktivní View
  • RxSwift zůstává alternativou pro projekty s iOS 8+, Combine — volba pro nové projekty na iOS 13+

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také