Combine — Schlüsselkonzepte, Publisher und reaktive Programmierung

Autor: IT Sectr Veröffentlicht: 2026-05-03 Lesezeit: 11 Min.

Combine ist Apples natives reaktives Programmierframework, eingeführt in iOS 13, macOS Catalina, tvOS 13 und watchOS 6. Es bietet eine deklarative Swift-API zur Verarbeitung asynchroner Ereignisse durch das Publisher-Subscriber-Muster und ersetzt Delegaten, Closures und NotificationCenter durch eine einheitliche Kette. Laut Apple, 2025 ist Combine die Grundlage für SwiftUI und moderne iOS-Architekturen und arbeitet eng mit async/await und Structured Concurrency zusammen. Das Framework wurde für die Komposition asynchroner Operationen mit Thread-Sicherheitsgarantien entwickelt.

Wesentliche Punkte

  • Combine ist Apples natives reaktives Framework mit Publisher-, Subscriber-, Subject-Typen und Fehlertypen durch Failure
  • Publisher emittiert Ereignisse im Zeitverlauf: Output-Werte, kann mit Erfolg oder Failure-Fehler abschließen
  • Subscriber empfängt Ereignisse vom Publisher und kann über Demand eine bestimmte Anzahl von Elementen anfordern
  • Operatoren map, flatMap, filter, combineLatest, zip, debounce transformieren Streams ohne Wettlaufsituationen
  • SwiftUI nutzt Combine über @Published, ObservableObject und @StateObject für reaktive UI-Updates

Was ist Combine?

Combine ist ein deklaratives reaktives Programmierframework, das in das Apple SDK integriert ist. Es implementiert das Reactive-Streams-Muster: Publisher produziert Werte, Subscriber konsumiert sie und Operatoren transformieren den Stream dazwischen. Combine löst das Problem von Callbacks und Delegaten, indem es ein einheitliches Kompositionsmodell für beliebige asynchrone Ereignisse bereitstellt — von Netzwerkantworten bis hin zu UI-Änderungen.

Vor Combine verwendeten iOS-Entwickler Drittanbieter-Bibliotheken, hauptsächlich RxSwift. Apple entwickelte Combine als native Alternative mit tiefer Integration in das Ökosystem: Das Framework unterstützt Objective-C über @objc-Brücken, arbeitet mit KVO (Key-Value Observing über NSObject.keyValuePublisher) und NotificationCenter und dient auch als Grundlage für SwiftUI. Alle in SwiftUI veröffentlichten UIKit-Komponenten verwenden Combine intern für Ansichtsaktualisierungen.

Combine wurde mit Blick auf Swift Concurrency entwickelt: Ab iOS 15 kann ein Publisher über .values in AsyncSequence umgewandelt und in for-await-in-Schleifen verwendet werden. Die umgekehrte Umwandlung von Async-Funktionen in Publisher erfolgt über Future. Laut Apple WWDC 2024 bleibt Combine trotz der Einführung von async/await für einmalige asynchrone Aufrufe das empfohlene Framework für die Verarbeitung von Streaming-Daten in UIKit-Anwendungen.

Grundlegende Combine-Konzepte

Combine basiert auf drei Protokollen: Publisher (emittiert Werte vom Typ Output, kann mit einem Fehler vom Typ Failure fehlschlagen), Subscriber (empfängt Werte, verwaltet Demand — die Anzahl der angeforderten Elemente), Subscription (stellt die Publisher-Subscriber-Verbindung mit Abbruchmöglichkeit dar). Der Datenkanal wird beim Subscribe initialisiert und endet bei Abbruch, Abschluss oder Fehler. Demand ist ein einzigartiges Combine-Konzept: Der Subscriber teilt dem Publisher mit, wie viele Elemente er verarbeiten kann, und implementiert so Gegensteuerung auf Protokollebene.

Publisher und Subscriber: Reaktive Stream-Architektur

Publisher ist ein Protokoll mit zwei zugehörigen Typen: Output (der Typ der emittierten Werte) und Failure (der Fehlertyp, der Error implementiert). Wenn der Stream nicht fehlschlagen kann, wird Failure als Never angegeben — dies garantiert dem Subscriber, dass onReceive nur mit Output aufgerufen wird. Zu den integrierten Publishern gehören Just (einzelner Wert), Sequence (Array), URLSession.DataTaskPublisher (Netzwerkanfrage), NotificationCenter.Publisher und der @Published-Property-Wrapper.

swift
import Combine

// Publisher aus einer Sequenz erstellen
let publisher = [1, 2, 3, 4, 5].publisher

// Subscriber mit Wertverarbeitung erstellen
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("Abgeschlossen")
    }
}

publisher.subscribe(PrintSubscriber())

Subscription und Demand

Subscription ist ein Protokoll, das die aktive Verbindung zwischen Publisher und Subscriber darstellt. Der Subscriber erhält Subscription in der Methode receive(subscription:) und ruft request(_:) auf, um Demand anzugeben: .unlimited (alle Werte), .max(N) (begrenzte Anzahl) oder .none (Pause). Demand kann sich dynamisch ändern — der Subscriber kann die Anzahl der angeforderten Elemente während des Datenempfangs erhöhen oder verringern. Dies ermöglicht Gegensteuerung ohne Pufferung auf der Publisher-Seite.

Subject und CurrentValueSubject

Subject ist ein Typ, der Publisher und Subscriber kombiniert. Ein Subject kann als Publisher (Abonnenten abonnieren ihn) und gleichzeitig als Subscriber (Werte werden an ihn gesendet) verwendet werden. Combine bietet zwei Arten von Subject: PassthroughSubject (speichert keinen Zustand, gibt nur neue Werte weiter) und CurrentValueSubject (speichert den aktuellen Wert und gibt ihn an neue Abonnenten weiter). Subject ist notwendig, um imperativen Code in reaktive Combine-Ketten zu integrieren.

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

// Als Publisher abonnieren
let cancellable = subject
    .map { $0.uppercased() }
    .sink { print($0) }

// Werte als Subscriber senden
subject.send("hello")  // Gibt "HELLO" aus
subject.send("world")  // Gibt "WORLD" aus

CurrentValueSubject unterscheidet sich von PassthroughSubject durch einen Anfangswert und eine Value-Eigenschaft: Ein Abonnent erhält sofort den aktuellen Wert beim Abonnieren und dann alle nachfolgenden Aktualisierungen. CurrentValueSubject.value ist les- und schreibbar — eine Änderung von value sendet automatisch den neuen Wert an alle Abonnenten. Dies macht CurrentValueSubject zur idealen Wahl für die Zustandsdarstellung in der MVVM-Architektur: ViewModel veröffentlicht CurrentValueSubject, View abonniert Änderungen über sink.

Beide Subjects können den Stream durch Aufruf von send(completion: .finished) oder send(completion: .failure(error)) beenden. Nach der Beendigung nimmt das Subject keine Ereignisse mehr an und sendet keine mehr aus. Für langlebige Streams, die nicht enden sollen (z. B. UI-Ereignisse), wird die Verwendung von PassthroughSubject mit Never Failure empfohlen, um ein versehentliches Aufrufen von send(completion:) zu vermeiden.

Transformations- und Kombinationsoperatoren

Combine-Operatoren sind Publisher-Methoden, die einen neuen Publisher zurückgeben. Jeder Operator erstellt ein neues Objekt, das den vorgelagerten Publisher abonniert und transformierte Werte nachgelagert ausgibt. Da Publisher ein generischer Typ ist, behalten Operatoren eine strenge Typisierung bei: map wandelt Output<A> in Output<B> um, tryMap fügt die Fehlermöglichkeit hinzu. Combine enthält etwa 100 integrierte Operatoren.

KategorieOperatorZweck
Transformationmap / tryMap / flatMapWerte oder Streams transformieren
Filterungfilter / compactMap / removeDuplicatesWerte auswählen oder bereinigen
KombinationcombineLatest / zip / mergeMehrere Publisher kombinieren
Zeitsteuerungdebounce / throttle / delayEreignisse verzögern und ausdünnen
Fehlerbehandlungcatch / retry / replaceErrorWiederherstellung nach Failure
Demand-Verwaltungbuffer / collectGruppierung oder Pufferung

flatMap in Combine unterscheidet sich wichtig von der RxSwift-Version: Es akzeptiert einen Closure, der einen Publisher mit demselben Failure-Typ zurückgibt, und entfaltet den verschachtelten Publisher in den Hauptstrom. flatMap mit maxPublishers: .max(1) verhält sich wie switchMap — es bricht den vorherigen verschachtelten Publisher ab, wenn ein neuer Wert eintrifft. Dies ist entscheidend für Suchszenarien: Wenn ein neues Zeichen eingegeben wird, wird die vorherige HTTP-Anfrage automatisch abgebrochen.

swift
// Debounce-Suche mit Abbruch der vorherigen Anfrage
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)

Kombinationsoperatoren — combineLatest und zip — funktionieren ähnlich wie in RxSwift: combineLatest gibt ein Tupel der neuesten Werte aller Publisher aus, wenn sich einer von ihnen ändert; zip paart Werte nach Index. merge kombiniert Publisher desselben Typs zu einem Stream, die Reihenfolge wird nicht garantiert. Combine verfügt auch über select — einen seltenen Operator, der den ersten abschließenden Publisher aus mehreren auswählt, und share — Multicasting eines Streams an mehrere Abonnenten ohne erneute Ausführung.

Scheduler und Thread-Verwaltung

Scheduler in Combine ist ein Protokoll, das den Ausführungskontext für Operatoren definiert. Im Gegensatz zu RxSwift mit seinen über 5 integrierten Schedulern verwendet Combine vorhandene Apple-Mechanismen: DispatchQueue, RunLoop und OperationQueue. Jeder dieser Typen entspricht dem Scheduler-Protokoll, sodass sie direkt an receive(on:) und subscribe(on:) ohne zusätzliche Adapter übergeben werden können.

receive(on:) schaltet den nachgelagerten Bereich auf den angegebenen Scheduler um — entspricht observeOn in RxSwift. Alle Operatoren nach receive(on:) werden auf dem angegebenen Scheduler ausgeführt. subscribe(on:) schaltet den vorgelagerten Bereich um — beeinflusst die Publisher-Ausführung. Ein typisches Muster: subscribe(on: DispatchQueue.global()) für Hintergrundarbeit und receive(on: DispatchQueue.main) für UI-Updates. In SwiftUI ist bei Verwendung von .onReceive keine integrierte Bindung an den Hauptthread erforderlich, aber für sink wird explizites receive(on:).main empfohlen.

swift
// Hintergrundladen + UI im Hauptthread
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 ist eine Alternative zu DispatchQueue.main für UI-Operationen. Der Unterschied besteht darin, dass RunLoop.main an den aktuellen Ereigniszyklus der Anwendung gebunden ist, während DispatchQueue.main an die globale Hauptwarteschlange gebunden ist. Für UIKit wird DispatchQueue.main empfohlen, für SwiftUI — RunLoop.main. ImmediateWhenScheduler führt Operationen synchron im aktuellen Thread aus — standardmäßig für Tests und einfache Publisher verwendet.

Combine und SwiftUI: Integration über ObservableObject

ObservableObject ist ein SwiftUI-Protokoll für Objekte, die Änderungen veröffentlichen. Eine Klasse, die ObservableObject implementiert, kann den @Published-Property-Wrapper für Eigenschaften verwenden, deren Änderungen SwiftUI automatisch über die Notwendigkeit einer Neuzeichnung informieren. Unter der Haube erstellt @Published einen Publisher, der den objectWillChange-Publisher benachrichtigt, wenn sich wrappedValue ändert. SwiftUI abonniert objectWillChange über @StateObject, @ObservedObject oder @EnvironmentObject.

@Published und @StateObject

@Published ist der häufigste Weg, Combine in SwiftUI zu integrieren. Wenn sich der Wert einer @Published-Eigenschaft ändert, aktualisiert SwiftUI alle Views, die dieses Objekt verwenden. @StateObject erstellt eine ObservableObject-Instanz und abonniert deren Änderungen. Eine mit @StateObject erstellte View wird automatisch neu gezeichnet, wenn sich @Published-Eigenschaften ändern. Wenn das Objekt zwischen mehreren Views geteilt werden muss, werden @ObservedObject oder @EnvironmentObject verwendet.

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 ist ein Typ-Auslöschungs-Wrapper für Cancellable, der ein Abonnement- Abbruchtoken speichert. Set<AnyCancellable> verwaltet den Lebenszyklus von Abonnements: Wenn der Besitzer deinitialisiert wird, werden alle Cancellable automatisch abgebrochen. In SwiftUI-Projekten wird Set<AnyCancellable> in der ObservableObject-Klasse deklariert, und Abonnements werden über .store(in: &cancellables) hinzugefügt. Für UIKit werden dieselben Mechanismen mit Speicherung in UIViewController über &cancellables oder manuelle cancel()-Aufrufe verwendet.

Combine vs RxSwift

Combine und RxSwift lösen dieselben reaktiven Programmieraufgaben, haben aber grundlegende architektonische Unterschiede. Combine ist Teil des Apple SDK mit Abwärtskompatibilität bis iOS 13, RxSwift ist eine Drittanbieter-Bibliothek mit Unterstützung für iOS 8+. Combine verwendet strenge Fehlertypisierung durch Failure-Generic, RxSwift verwendet einen einzigen Error-Typ. Combine ist auf Plattformebene in SwiftUI integriert, RxSwift benötigt RxCocoa für UI-Erweiterungen.

Die Wahl zwischen Combine und RxSwift hängt von den Projektanforderungen ab. Wenn die Mindest-iOS-Version >= 13 ist und das Projekt SwiftUI verwendet, ist Combine dank der integrierten Integration und fehlender zusätzlicher Abhängigkeiten die natürliche Wahl. Wenn das Projekt iOS 11-12 unterstützt, eine vorhandene RxSwift-Codebasis enthält oder spezifische Operatoren erfordert, die nur in RxSwift verfügbar sind (z. B. Observable.from(path:)), bleibt RxSwift eine gültige Lösung.

EigenschaftCombineRxSwift
EntwicklerApple (im SDK integriert)ReactiveX (Community)
iOS-VersioniOS 13+iOS 8+
FehlertypGenerisches Failure (Never für UI)Error (beliebig)
UI-Integration@Published + SwiftUIRxCocoa + UIKit
Operatoren~100 integriert400+ Operatoren
Swift ConcurrencyÜber .values (Async-Sequenz)Über Brückenbibliothek

Häufig gestellte Fragen

Was ist der Unterschied zwischen PassthroughSubject und CurrentValueSubject?

PassthroughSubject speichert keinen Zustand — ein Abonnent erhält nur Ereignisse, die nach dem Abonnement gesendet wurden. CurrentValueSubject speichert den aktuellen Wert und übergibt ihn jedem neuen Abonnenten sofort bei der Anmeldung. CurrentValueSubject eignet sich zur Darstellung von Zuständen (z. B. isLoggedIn).

Wie kündigt man ein Abonnement in Combine?

Ein Abonnement gibt AnyCancellable zurück, das durch Aufruf von cancel() oder bei der Deinitialisierung abgebrochen wird. Für die Gruppenverwaltung verwenden Sie Set<AnyCancellable> — alle Abonnements werden beim Löschen des Sets abgebrochen. Dies ist analog zu DisposeBag in RxSwift.

Muss ich Combine nach async/await lernen?

Ja, Combine bleibt für Streaming-Daten relevant: UI-Ereignisse, debounce, combineLatest, WebSocket. async/await ist praktisch für einmalige Anfragen, Combine — für kontinuierliche oder mehrere Streams. Beide Frameworks ergänzen sich — Publisher kann in AsyncSequence umgewandelt werden.

Wie verwendet man Combine mit UIKit?

UIKit hat keine integrierten Publisher, aber Apple bietet Erweiterungen: NotificationCenter.default.publisher(for:), Timer.publish, URLSession.dataTaskPublisher. Für benutzerdefinierte UI-Ereignisse werden PassthroughSubject oder @IBAction verwendet, die über Future oder Subject in einen Publisher eingewickelt werden.

Was ist Gegensteuerung (Backpressure) in Combine?

Gegensteuerung (Backpressure) ist ein Mechanismus zur Steuerung der Flussgeschwindigkeit: Der Subscriber teilt dem Publisher über Demand mit, wie viele Elemente er verarbeiten kann. Wenn Demand = .max(1) ist, wartet der Publisher auf eine Anfrage, bevor er den nächsten Wert sendet. Dies verhindert Pufferüberlauf bei nicht übereinstimmenden Geschwindigkeiten von Produzent und Konsument.

Zusammenfassung

  • Combine ist Apples natives reaktives Framework für iOS 13+ mit Publisher-Subscriber-Muster und strenger Fehlertypisierung
  • Publisher und Subscriber bilden einen Kommunikationskanal mit Demand-Verwaltung für Gegensteuerung
  • Subject (Passthrough und CurrentValue) integrieren imperativen Code in reaktive Ketten
  • Operatoren map, flatMap, combineLatest, debounce, catch bieten deklarative Stream-Verarbeitung
  • Scheduler über DispatchQueue und RunLoop verwalten Ausführungsthreads ohne UI-Blockierung
  • SwiftUI verwendet Combine über @Published, ObservableObject und @StateObject für reaktive Views
  • RxSwift bleibt eine Alternative für Projekte mit iOS 8+, Combine ist die Wahl für neue Projekte auf iOS 13+

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch