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 — 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í.
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 — 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.
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 — 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 — 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.
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 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ů.
| Kategorie | Operátor | Účel |
|---|---|---|
| Transformace | map / tryMap / flatMap | Transformace hodnot nebo toků |
| Filtrování | filter / compactMap / removeDuplicates | Výběr nebo čištění hodnot |
| Kombinace | combineLatest / zip / merge | Spojování více Publisherů |
| Správa času | debounce / throttle / delay | Zpoždění a zřeďování událostí |
| Správa chyb | catch / retry / replaceError | Obnova po Failure |
| Správa Demand | buffer / collect | Seskupová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.
// 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í.
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.
// 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é.
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 — 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.
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 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.
| Vlastnost | Combine | RxSwift |
|---|---|---|
| Vývojář | Apple (vestavěný v SDK) | ReactiveX (komunita) |
| Verze iOS | iOS 13+ | iOS 8+ |
| Typ chyby | Generic Failure (Never pro UI) | Error (jakákoli) |
| UI integrace | @Published + SwiftUI | RxCocoa + UIKit |
| Operátory | ~100 vestavěných | 400+ operátorů |
| Swift Concurrency | Přes .values (async sequence) | Přes mostovou knihovnu |
Často kladené otázky
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).
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.
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.
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.
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í
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í.
Přečtěte si také