Combine — kulcsfogalmak, Publisher és reaktív programozás

Szerző: IT Sectr Megjelenés: 2026-05-03 Olvasási idő: 11 perc

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 — natív reaktív Apple-keretrendszer Publisher, Subscriber, Subject típusokkal és hibatípusokkal Failure-en keresztül
  • Publisher eseményeket bocsát ki időben: Output értékeket, sikeresen vagy Failure hibával végződhet
  • Subscriber eseményeket fogad a Publisher-től, és Demand-en keresztül meghatározott számú elemet kérhet
  • Operátorok map, flatMap, filter, combineLatest, zip, debounce versenyhelyzetek nélkül alakítják át a folyamokat
  • SwiftUI a Combine-t használja @Published, ObservableObject és @StateObject segítségével a felület reaktív frissítéséhez

Mi a Combine?

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 alapvető fogalmai

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 és Subscriber: a reaktív folyam architektúrája

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.

swift
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 és Demand

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 és CurrentValueSubject

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.

swift
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.

Transzformációs és kombinációs operátorok

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óriaOperátorCél
Transzformációmap / tryMap / flatMapÉrtékek vagy folyamok átalakítása
Szűrésfilter / compactMap / removeDuplicatesÉrtékek kiválasztása vagy tisztítása
KombinációcombineLatest / zip / mergeTöbb Publisher egyesítése
Időkezelésdebounce / throttle / delayEsemények késleltetése és ritkítása
Hibakezeléscatch / retry / replaceErrorHelyreállítás Failure után
Demand-kezelésbuffer / collectCsoportosí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.

swift
// 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.

Schedulerek és szálkezelés

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.

swift
// 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.

Combine és SwiftUI: integráció ObservableObject segítségével

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 és @StateObject

@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.

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 — 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 vs RxSwift

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őCombineRxSwift
FejlesztőApple (SDK-ba építve)ReactiveX (közösség)
iOS verzióiOS 13+iOS 8+
Hiba típusaGeneric Failure (Never UI-hoz)Error (bármilyen)
UI integráció@Published + SwiftUIRxCocoa + UIKit
Operátorok~100 beépített400+ operátor
Swift Concurrency.values segítségével (async sequence)Híd könyvtáron keresztül

Gyakran ismételt kérdések

Mi a különbség a PassthroughSubject és a CurrentValueSubject között?

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).

Hogyan törölhető a feliratkozás a Combine-ben?

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.

Szükséges-e megtanulni a Combine-t az async/await után?

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ó.

Hogyan használható a Combine UIKit-kel?

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.

Mi a backpressure a Combine-ben?

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

  • Combine — natív reaktív Apple-keretrendszer iOS 13+ rendszerhez Publisher-Subscriber mintával és szigorú hibatipizálással
  • Publisher és Subscriber kommunikációs csatornát alkotnak Demand-kezeléssel a backpressure számára
  • Subject (Passthrough és CurrentValue) integrálja az imperatív kódot a reaktív láncokba
  • Operátorok map, flatMap, combineLatest, debounce, catch deklaratív folyamfeldolgozást biztosítanak
  • Schedulerek DispatchQueue és RunLoop segítségével kezelik a végrehajtási szálakat UI blokkolás nélkül
  • SwiftUI a Combine-t használja @Published, ObservableObject és @StateObject segítségével reaktív View-khoz
  • RxSwift továbbra is alternatíva iOS 8+ projektekhez, a Combine — választás új iOS 13+ projektekhez

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.

Projekt megbeszélése

Olvassa el is