Combine — az Apple natív reaktív programozási keretrendszere, amely az iOS 13, macOS Catalina, tvOS 13 és watchOS 6 rendszerekben került bevezetésre. Deklaratív Swift API-t biztosít aszinkron események feldolgozásához a Publisher és Subscriber minta segítségével, felváltva a delegate-eket, closure-öket és a NotificationCenter-t egy egységes lánccal. A Apple, 2025 szerint a Combine a SwiftUI és a modern iOS-architektúrák alapja, szoros kapcsolatban működve az async/await és a Structured Concurrency rendszerekkel. A keretrendszer aszinkron műveletek kompozíciójára lett tervezve szálbiztonsági garanciákkal.
Főbb pontok
Combine — egy deklaratív reaktív programozási keretrendszer, amely az Apple SDK-ba van beépítve. A Reactive Streams mintát valósítja meg: a Publisher értékeket állít elő, a Subscriber azokat fogyasztja, és az operátorok alakítják át a folyamot közöttük. A Combine megoldja a callback-ek és delegate-ek problémáját, egységes kompozíciós modellt biztosítva bármilyen aszinkron eseményhez — a hálózati válaszoktól az UI-változásokig.
A Combine megjelenése előtt az iOS-fejlesztők harmadik féltől származó könyvtárakat használtak, elsősorban a RxSwift-et. Az Apple a Combine-t natív alternatívaként hozta létre mély ökoszisztéma-integrációval: a keretrendszer támogatja az Objective-C-t @objc hidakon keresztül, együttműködik a KVO-val (Key-Value Observing NSObject.keyValuePublisher segítségével) és a NotificationCenter-rel, valamint a SwiftUI alapjául szolgál. A SwiftUI-ben publikált összes UIKit-komponens a Combine-t használja a nézetek frissítéséhez.
A Combine a Swift Concurrency figyelembevételével lett tervezve: iOS 15-től kezdve a Publisher .values segítségével AsyncSequence-vé alakítható és for-await-in ciklusokban használható. Az async függvények Publisher-vé történő visszaalakítása Future segítségével történik. A Apple WWDC 2024 szerint a Combine továbbra is ajánlott keretrendszer az adatfolyamok feldolgozásához UIKit alkalmazásokban, annak ellenére, hogy az egyszeri aszinkron hívásokhoz megjelent az async/await.
A Combine három protokollon alapul: Publisher (Output típusú értékeket bocsát ki, Failure típusú hibával végződhet), Subscriber (értékeket fogad, kezeli a Demand-ot — a kért elemek számát), Subscription (a Publisher-Subscriber kapcsolatot reprezentálja törlési lehetőséggel). Az adatátviteli csatorna a subscribe-kor inicializálódik és a cancel, completion vagy error esetén ér véget. Demand — a Combine egyedi koncepciója: a Subscriber tájékoztatja a Publisher-t, hogy hány elemet képes feldolgozni, backpressure-t megvalósítva protokoll szinten.
Publisher — protokoll két kapcsolódó típussal: Output (a kibocsátott értékek típusa) és Failure (az Error-t megvalósító hibatípus). Ha a folyam nem végződhet hibával, a Failure Never-ként van megadva — ez garantálja a Subscriber számára, hogy az onReceive csak Output-tal lesz meghívva. A beépített Publisher-ek közé tartozik a Just (egyetlen érték), Sequence (tömb), URLSession.DataTaskPublisher (hálózati kérés), NotificationCenter.Publisher és a @Published property wrapper.
import Combine
// Publisher létrehozása sorozatból
let publisher = [1, 2, 3, 4, 5].publisher
// Subscriber létrehozása értékfeldolgozással
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("Befejezve")
}
}
publisher.subscribe(PrintSubscriber())
Subscription — egy protokoll, amely az aktív kapcsolatot reprezentálja Publisher és Subscriber között. A Subscriber megkapja a Subscription-t a receive(subscription:) metódusban, és meghívja a request(_:) függvényt a Demand megadásához: .unlimited (összes érték), .max(N) (korlátozott szám) vagy .none (szünet). A Demand dinamikusan változhat — a Subscriber növelheti vagy csökkentheti a kért elemek számát az adatfogadás során. Ez backpressure-t biztosít pufferképződés nélkül a Publisher oldalán.
Subject — egy típus, amely egyesíti a Publisher-t és a Subscriber-t. A Subject használható Publisher-ként (fel lehet rá iratkozni) és egyidejűleg Subscriber-ként (értékeket lehet küldeni bele). A Combine kétféle Subject-et kínál: PassthroughSubject (nem tárol állapotot, csak új értékeket továbbít) és CurrentValueSubject (tárolja az aktuális értéket, és továbbítja azt az új feliratkozóknak). A Subject szükséges az imperatív kód reaktív Combine-láncokba való integrálásához.
let subject = PassthroughSubject<String, Never>()
// Feliratkozás Publisher-ként
let cancellable = subject
.map { $0.uppercased() }
.sink { print($0) }
// Értékek küldése Subscriber-ként
subject.send("hello") // Kiírja: „HELLO"
subject.send("világ") // Kiírja: „WORLD"
CurrentValueSubject abban különbözik a PassthroughSubject-től, hogy rendelkezik kezdeti értékkel és value tulajdonsággal: a feliratkozó azonnal megkapja az aktuális értéket a feliratkozáskor, majd az összes későbbi frissítést. A CurrentValueSubject.value olvasható és írható — a value megváltoztatása automatikusan elküldi az új értéket az összes feliratkozónak. Ez teszi a CurrentValueSubject-t ideális választássá az állapot reprezentálására az MVVM architektúrában: a ViewModel közzéteszi a CurrentValueSubject-t, a View a változásokkal együtt feliratkozik a sink segítségével.
Mindkét Subject lehetővé teszi a folyam befejezését a send(completion: .finished) vagy send(completion: .failure(error)) meghívásával. Befejezés után a Subject leállítja az események fogadását és továbbítását. Hosszú élettartamú folyamokhoz, amelyek nem végződhetnek (pl. UI-események), ajánlott a PassthroughSubject használata Never Failure-lel a véletlen send(completion:) hívás kizárása érdekében.
Operátorok Combine — a Publisher metódusai, amelyek új Publisher-t adnak vissza. Minden operátor létrehoz egy új objektumot, amely feliratkozik a felső Publisher-re, és átalakított értékeket bocsát ki lefelé. Mivel a Publisher egy generikus típus, az operátorok megőrzik a szigorú típusosságot: a map Output<A>-t Output<B>-vé alakítja, a tryMap hibalehetőséget ad hozzá. A Combine körülbelül 100 beépített operátort tartalmaz.
| Kategória | Operátor | Cél |
|---|---|---|
| Transzformáció | map / tryMap / flatMap | Értékek vagy folyamok átalakítása |
| Szűrés | filter / compactMap / removeDuplicates | Értékek kiválasztása vagy tisztítása |
| Kombináció | combineLatest / zip / merge | Több Publisher egyesítése |
| Időkezelés | debounce / throttle / delay | Események késleltetése és ritkítása |
| Hibakezelés | catch / retry / replaceError | Helyreállítás Failure után |
| Demand-kezelés | buffer / collect | Csoportosítás vagy puffere |
flatMap a Combine-ben fontos különbséggel rendelkezik az RxSwift verzióhoz képest: egy closure-t fogad, amely ugyanolyan Failure típusú Publisher-t ad vissza, és kicsomagolja a beágyazott Publisher-t a fő folyamba. A flatMap maxPublishers: .max(1) paraméterrel switchMap-ként viselkedik — törli az előző beágyazott Publisher-t egy új érték érkezésekor. Ez kritikus a keresési forgatókönyveknél: új karakter beírásakor az előző HTTP-kérés automatikusan törlődik.
// Debounce keresés az előző kérés törlésével
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)
Kombinációs operátorok — a combineLatest és zip — hasonlóan működnek, mint az RxSwift-ben: a combineLatest az összes Publisher utolsó értékeinek tuple-jét bocsátja ki bármelyikük megváltozásakor; a zip az értékeket páronként kombinálja index szerint. A merge azonos típusú Publisher-eket egyesít egy folyamba, a preserveOrder nem garantált. A Combine-ban létezik még a select — egy ritka operátor, amely kiválasztja az első befejeződő Publisher-t több közül, és a share — a folyam multicast-ja több feliratkozóhoz ismételt végrehajtás nélkül.
Scheduler a Combine-ben — egy protokoll, amely meghatározza a végrehajtási kontextust az operátorok számára. Ellentétben az RxSwift-tel, amely 5+ beépített Scheduler-rel rendelkezik, a Combine a meglévő Apple mechanizmusokat használja: DispatchQueue, RunLoop és OperationQueue. Ezen típusok mindegyike megfelel a Scheduler protokollnak, lehetővé téve közvetlen átadásukat a receive(on:) és subscribe(on:) függvényeknek további adapterek nélkül.
receive(on:) átkapcsolja a downstream-et a megadott Scheduler-re — az observeOn megfelelője az RxSwift-ben. Az összes receive(on:) utáni operátor a megadott Scheduler-en hajtódik végre. subscribe(on:) átkapcsolja az upstream-et — befolyásolja a Publisher végrehajtását. Tipikus minta: subscribe(on: DispatchQueue.global()) háttérmunkához és receive(on: DispatchQueue.main) UI-frissítéshez. A SwiftUI-ben a .onReceive használatakor a főszálhoz való beépített kötés nem szükséges, de a sink esetében javasolt az explicit receive(on:).main.
// Háttér letöltés + UI a főszálon
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 — alternatíva a DispatchQueue.main helyett UI-műveletekhez. A különbség az, hogy a RunLoop.main az alkalmazás aktuális eseményciklusához van kötve, míg a DispatchQueue.main a főszál globális sorához. UIKit esetében a DispatchQueue.main javasolt, SwiftUI esetében — a RunLoop.main. ImmediateWhenScheduler szinkron módon hajtja végre a műveleteket az aktuális szálon — alapértelmezés szerint tesztekhez és egyszerű Publisher-ekhez használatos.
ObservableObject — SwiftUI protokoll olyan objektumok számára, amelyek változásokat tesznek közzé. Az ObservableObject-t megvalósító osztály használhatja a @Published property wrapper-t olyan tulajdonságokhoz, amelyek változásai automatikusan értesítik a SwiftUI-t az újrarajzolás szükségességéről. A @Published a háttérben létrehoz egy Publisher-t, amely értesíti az objectWillChange Publisher-t a wrappedValue megváltozásakor. A SwiftUI az objectWillChange-re iratkozik fel a @StateObject, @ObservedObject vagy @EnvironmentObject segítségével.
@Published — a Combine SwiftUI-ba történő integrálásának legelterjedtebb módja. Amikor egy @Published tulajdonság értéke megváltozik, a SwiftUI frissíti az összes View-t, amely ezt az objektumot használja. @StateObject létrehoz egy ObservableObject példányt, és feliratkozik annak változásaira. A @StateObject-val létrehozott View automatikusan újrarajzolódik a @Published tulajdonságok változásakor. Ha az objektumot több View között kell megosztani, a @ObservedObject vagy @EnvironmentObject használatos.
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 — típus-törlő a Cancellable számára, amely tárolja a feliratkozás törlési tokenjét. A Set<AnyCancellable> kezeli a feliratkozások életciklusát: a tulajdonos deinicializálásakor az összes Cancellable automatikusan törlődik. SwiftUI projektekben a Set<AnyCancellable> az ObservableObject osztályban van deklarálva, a feliratkozások pedig a .store(in: &cancellables) segítségével kerülnek hozzáadásra. UIKit esetében ugyanezek a mechanizmusok használatosak a UIViewController-ben való tárolással a &cancellables segítségével vagy a cancel() manuális meghívásával.
Combine és RxSwift ugyanazokat a reaktív programozási feladatokat oldják meg, de alapvető architektúrabeli különbségekkel rendelkeznek. A Combine — az Apple SDK része, visszafelé kompatibilitással iOS 13-ig, az RxSwift — harmadik féltől származó könyvtár iOS 8+ támogatással. A Combine szigorú hibatipizálást használ a Failure generic segítségével, az RxSwift — az egységes Error típust. A Combine platformszinten integrált a SwiftUI-val, az RxSwift RxCocoa-t igényel a UI-bővítményekhez.
A Combine és RxSwift közötti választás a projekt követelményeitől függ. Ha a minimális iOS verzió >= 13 és a projekt SwiftUI-t használ — a Combine a természetes választás a beépített integrációnak és a további függőségek hiányának köszönhetően. Ha a projekt iOS 11-12-t támogat, meglévő RxSwift kódbázissal rendelkezik, vagy olyan specifikus operátorokat igényel, amelyek csak az RxSwift-ben érhetők el (pl. Observable.from(path:)), — az RxSwift továbbra is indokolt megoldás marad.
| Jellemző | Combine | RxSwift |
|---|---|---|
| Fejlesztő | Apple (SDK-ba építve) | ReactiveX (közösség) |
| iOS verzió | iOS 13+ | iOS 8+ |
| Hiba típusa | Generic Failure (Never UI-hoz) | Error (bármilyen) |
| UI integráció | @Published + SwiftUI | RxCocoa + UIKit |
| Operátorok | ~100 beépített | 400+ operátor |
| Swift Concurrency | .values segítségével (async sequence) | Híd könyvtáron keresztül |
Gyakran ismételt kérdések
PassthroughSubject nem tárol állapotot — a feliratkozó csak a feliratkozás után küldött eseményeket kapja. CurrentValueSubject tárolja az aktuális értéket, és azt minden új feliratkozónak azonnal átadja a feliratkozáskor. A CurrentValueSubject alkalmas állapot reprezentálására (pl. isLoggedIn).
A feliratkozás AnyCancellable-t ad vissza, amely a cancel() hívásakor vagy deinicializáláskor törlődik. Csoportos kezeléshez használjon Set<AnyCancellable>-t — a set törlésekor az összes feliratkozás törlődik. Ez az RxSwift DisposeBag-jének megfelelője.
Igen, a Combine továbbra is releváns az adatfolyamokhoz: UI események, debounce, combineLatest, WebSocket. Az async/await egyszeri kérésekhez kényelmes, a Combine — folyamatos vagy többszörös folyamokhoz. A két keretrendszer kiegészíti egymást — a Publisher AsyncSequence-vé alakítható.
A UIKit nem rendelkezik beépített Publisher-ekkel, de az Apple kiterjesztéseket biztosít: NotificationCenter.default.publisher(for:), Timer.publish, URLSession.dataTaskPublisher. Egyedi UI eseményekhez PassthroughSubject vagy @IBAction használatos, Publisher-be csomagolva Future vagy Subject segítségével.
Backpressure — a folyam sebességének szabályozó mechanizmusa: a Subscriber a Demand segítségével tájékoztatja a Publisher-t, hogy hány elemet képes feldolgozni. Ha a Demand = .max(1), a Publisher megvárja a kérést a következő érték elküldése előtt. Ez megakadályozza a puffer túlcsordulását, ha a termelő és a fogyasztó sebessége nem egyezik.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is