Combine — nyckelbegrepp, Publisher och reaktiv programmering

Författare: IT Sectr Publicerad: 2026-05-03 Lästid: 11 min

Combine — är ett inbyggt reaktivt programmeringsramverk från Apple, introducerat i iOS 13, macOS Catalina, tvOS 13 och watchOS 6. Det tillhandahåller ett deklarativt Swift API för att bearbeta asynkrona händelser genom Publisher- och Subscriber-mönstret, och ersätter delegater, closures och NotificationCenter med en enhetlig kedja. Enligt Apple, 2025 är Combine grunden för SwiftUI och moderna iOS-arkitekturer, och arbetar i nära samverkan med async/await och Structured Concurrency. Ramverket är utformat för komposition av asynkrona operationer med garantier för trådsäkerhet.

Huvudpunkter

  • Combine — inbyggt reaktivt Apple-ramverk med typerna Publisher, Subscriber, Subject och feltyper via Failure
  • Publisher sänder ut händelser över tid: Output-värden, kan avslutas med framgång eller fel Failure
  • Subscriber tar emot händelser från Publisher och kan begära ett visst antal element via Demand
  • Operatorer map, flatMap, filter, combineLatest, zip, debounce transformerar flöden utan racing-förhållanden
  • SwiftUI använder Combine via @Published, ObservableObject och @StateObject för reaktiv uppdatering av gränssnittet

Vad är Combine?

Combine — är ett deklarativt reaktivt programmeringsramverk inbyggt i Apple SDK. Det implementerar Reactive Streams-mönstret: Publisher producerar värden, Subscriber konsumerar dem och operatorer transformerar flödet mellan dem. Combine löser problemet med callbacks och delegater genom att tillhandahålla en enhetlig kompositionsmodell för alla asynkrona händelser — från nätverkssvar till UI-ändringar.

Före Combles ankomst använde iOS-utvecklare tredjepartsbibliotek, främst RxSwift. Apple skapade Combine som ett inbyggt alternativ med djup integration i ekosystemet: ramverket stöder Objective-C via @objc-bryggor, arbetar med KVO (Key-Value Observing via NSObject.keyValuePublisher) och NotificationCenter, och är också grunden för SwiftUI. Alla UIKit-komponenter som publiceras i SwiftUI använder Combine under huven för att uppdatera vyer.

Combine är utformat med Swift Concurrency i åtanke: från och med iOS 15 kan Publisher omvandlas till AsyncSequence via .values och användas i for-await-in-loopar. Den omvända konverteringen av async-funktioner till Publisher görs via Future. Enligt Apple WWDC 2024 förblir Combine det rekommenderade ramverket för bearbetning av strömmande data i UIKit-applikationer, trots tillkomsten av async/await för enstaka asynkrona anrop.

Grundläggande begrepp i Combine

Combine är baserat på tre protokoll: Publisher (sänder ut värden av typen Output, kan avslutas med fel av typen Failure), Subscriber (tar emot värden, hanterar Demand — antalet begärda element), Subscription (representerar Publisher-Subscriber-anslutningen med möjlighet till avbokning). Datakanalen initieras vid subscribe och avslutas vid cancel, completion eller error. Demand — unikt koncept i Combine: Subscriber informerar Publisher om hur många element det är redo att bearbeta, och implementerar backpressure på protokollnivå.

Publisher och Subscriber: arkitektur för reaktivt flöde

Publisher — protokoll med två associerade typer: Output (typ av sända värden) och Failure (feltyp som implementerar Error). Om flödet inte kan avslutas med fel, anges Failure som Never — detta garanterar Subscriber att onReceive endast anropas med Output. Inbyggda Publishers inkluderar Just (enstaka värde), Sequence (array), URLSession.DataTaskPublisher (nätverksbegäran), NotificationCenter.Publisher och @Published property wrapper.

swift
import Combine

// Skapa Publisher från sekvens
let publisher = [1, 2, 3, 4, 5].publisher

// Skapa Subscriber med värdebearbetning
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("Slutfört")
    }
}

publisher.subscribe(PrintSubscriber())

Subscription och Demand

Subscription — är ett protokoll som representerar den aktiva anslutningen mellan Publisher och Subscriber. Subscriber tar emot Subscription i metoden receive(subscription:) och anropar request(_:) för att ange Demand: .unlimited (alla värden), .max(N) (begränsat antal) eller .none (paus). Demand kan ändras dynamiskt — Subscriber kan öka eller minska antalet begärda element under mottagning av data. Detta säkerställer backpressure utan buffring på Publishers sida.

Subject och CurrentValueSubject

Subject — är en typ som kombinerar Publisher och Subscriber. Subject kan användas som Publisher (man kan prenumerera på det) och samtidigt som Subscriber (man kan skicka värden till det). Combine erbjuder två typer av Subject: PassthroughSubject (lagrar inte tillstånd, vidarebefordrar endast nya värden) och CurrentValueSubject (lagrar det aktuella värdet och vidarebefordrar det till nya prenumeranter). Subject är nödvändigt för integration av imperativ kod i reaktiva Combine-kedjor.

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

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

// Skicka värden som Subscriber
subject.send("hello")  // Skriver ut “HELLO”
subject.send("världen")  // Skriver ut “WORLD”

CurrentValueSubject skiljer sig från PassthroughSubject genom närvaron av ett initialvärde och egenskapen value: prenumeranten får omedelbart det aktuella värdet vid prenumeration och därefter alla efterföljande uppdateringar. CurrentValueSubject.value är tillgänglig för läsning och skrivning — ändring av value skickar automatiskt det nya värdet till alla prenumeranter. Detta gör CurrentValueSubject till det idealiska valet för att representera tillstånd i MVVM-arkitektur: ViewModel publicerar CurrentValueSubject, View prenumererar med förändringar via sink.

Båda Subjekten tillåter avslutning av flödet genom att anropa send(completion: .finished) eller send(completion: .failure(error)). Efter avslutning slutar Subject att ta emot och vidarebefordra händelser. För långvariga flöden som inte bör avslutas (t.ex. UI-händelser), rekommenderas användning av PassthroughSubject med Never Failure för att utesluta oavsiktligt anrop av send(completion:).

Transformations- och kombinationsoperatorer

Operatorer Combine — är metoder på Publisher som returnerar en ny Publisher. Varje operator skapar ett nytt objekt som prenumererar på den uppströms Publisher och sänder transformerade värden nedströms. Eftersom Publisher är en generisk typ, behåller operatorer strikt typning: map transformerar Output<A> till Output<B>, tryMap lägger till felmöjlighet. Combine innehåller cirka 100 inbyggda operatorer.

KategoriOperatorSyfte
Transformationmap / tryMap / flatMapTransformering av värden eller flöden
Filtreringfilter / compactMap / removeDuplicatesUrval eller rensning av värden
KombinationcombineLatest / zip / mergeKombinera flera Publishers
Tidshanteringdebounce / throttle / delayFördröjning och förtunning av händelser
Felhanteringcatch / retry / replaceErrorÅterhämtning efter Failure
Demand-hanteringbuffer / collectGruppering eller buffring

flatMap i Combine har en viktig skillnad jämfört med RxSwift-versionen: den accepterar en closure som returnerar en Publisher med samma Failure-typ och vecklar ut den nästlade Publisher i huvudflödet. flatMap med maxPublishers: .max(1) beter sig som switchMap — avbryter den tidigare nästlade Publisher när ett nytt värde anländer. Detta är kritiskt för sökscenarier: när en ny tecken matas in, avbryts den tidigare HTTP-begäran automatiskt.

swift
// Debounce-sökning med annullering av tidigare begäran
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)

Kombinationsoperatorer — combineLatest och zip — fungerar liknande som i RxSwift: combineLatest sänder en tupel av de senaste värdena från alla Publishers när någon av dem ändras; zip kombinerar värden parvis efter index. merge slår samman Publishers av samma typ till ett flöde, preserveOrder är inte garanterat. I Combine finns också select — en sällsynt operator som väljer den första avslutade Publisher bland flera, och share — multicast av flödet till flera prenumeranter utan upprepad exekvering.

Schedulers och trådhantering

Scheduler i Combine — är ett protokoll som definierar exekveringskontext för operatorer. Till skillnad från RxSwift med sina 5+ inbyggda Schedulers, använder Combine befintliga Apple-mekanismer: DispatchQueue, RunLoop och OperationQueue. Var och en av dessa typer motsvarar Scheduler-protokollet, vilket möjliggör direkt överföring till receive(on:) och subscribe(on:) utan extra adaptrar.

receive(on:) växlar nedströms till angiven Scheduler — motsvarigheten till observeOn i RxSwift. Alla operatorer efter receive(on:) exekveras på angiven Scheduler. subscribe(on:) växlar uppströms — påverkar Publishers exekvering. Typiskt mönster: subscribe(on: DispatchQueue.global()) för bakgrundsarbete och receive(on: DispatchQueue.main) för UI-uppdatering. I SwiftUI vid användning av .onReceive krävs inte inbyggd bindning till huvudtråden, men för sink rekommenderas explicit receive(on:).main.

swift
// Bakgrundsnedladdning + UI på huvudtråden
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 — alternativ till DispatchQueue.main för UI-operationer. Skillnaden är att RunLoop.main är bunden till applikationens aktuella händelseloop, medan DispatchQueue.main är bunden till huvudtrådens globala kö. För UIKit rekommenderas DispatchQueue.main, för SwiftUI — RunLoop.main. ImmediateWhenScheduler utför operationer synkront på aktuell tråd — används som standard för tester och enkla Publishers.

Combine och SwiftUI: integration via ObservableObject

ObservableObject — SwiftUI-protokoll för objekt som publicerar ändringar. En klass som implementerar ObservableObject kan använda property wrapper @Published för egenskaper vars ändringar automatiskt meddelar SwiftUI om behovet av omritning. Under huven skapar @Published en Publisher som meddelar objectWillChange Publisher vid ändring av wrappedValue. SwiftUI prenumererar på objectWillChange via @StateObject, @ObservedObject eller @EnvironmentObject.

@Published och @StateObject

@Published — det vanligaste sättet att integrera Combine i SwiftUI. När värdet på en @Published-egenskap ändras, uppdaterar SwiftUI alla Vyer som använder detta objekt. @StateObject skapar en instans av ObservableObject och prenumererar på dess ändringar. En Vy skapad med @StateObject omritas automatiskt vid ändring av @Published-egenskaper. Om objektet måste överföras mellan flera Vyer används @ObservedObject eller @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-torkare för Cancellable, som lagrar annulleringstoken för prenumerationen. Set<AnyCancellable> hanterar livscykeln för prenumerationer: vid avinitialisering av ägaren annulleras alla Cancellable automatiskt. I SwiftUI-projekt deklareras Set<AnyCancellable> i ObservableObject-klassen och prenumerationer läggs till via .store(in: &cancellables). För UIKit används samma mekanismer med lagring i UIViewController via &cancellables eller manuellt anrop av cancel().

Combine vs RxSwift

Combine och RxSwift löser samma reaktiva programmeringsuppgifter men har fundamentala arkitekturskillnader. Combine — del av Apple SDK med bakåtkompatibilitet till iOS 13, RxSwift — tredjepartsbibliotek med stöd för iOS 8+. Combine använder strikt feltypning via Failure generic, RxSwift — den enhetliga typen Error. Combine är integrerad med SwiftUI på plattformsnivå, RxSwift kräver RxCocoa för UI-tillägg.

Valet mellan Combine och RxSwift beror på projektets krav. Om minsta iOS-version >= 13 och projektet använder SwiftUI — är Combine det naturliga valet tack vare inbyggd integration och frånvaro av extra beroenden. Om projektet stöder iOS 11-12, innehåller en befintlig RxSwift-kodbas eller kräver specifika operatorer som endast finns i RxSwift (t.ex. Observable.from(path:)), — förblir RxSwift en motiverad lösning.

EgenskapCombineRxSwift
UtvecklareApple (inbyggt i SDK)ReactiveX (community)
iOS-versioniOS 13+iOS 8+
FeltypGeneric Failure (Never för UI)Error (vilken som helst)
UI-integration@Published + SwiftUIRxCocoa + UIKit
Operatorer~100 inbyggda400+ operatorer
Swift ConcurrencyVia .values (async sequence)Via bryggbibliotek

Vanliga frågor

Vad är skillnaden mellan PassthroughSubject och CurrentValueSubject?

PassthroughSubject lagrar inte tillstånd — prenumeranten får endast händelser som skickats efter prenumerationen. CurrentValueSubject lagrar det aktuella värdet och överför det till varje ny prenumerant omedelbart vid prenumeration. CurrentValueSubject är lämplig för att representera tillstånd (t.ex. isLoggedIn).

Hur avbokar man en prenumeration i Combine?

Prenumerationen returnerar AnyCancellable, som annulleras vid anrop av cancel() eller vid avinitialisering. För grupphantering använd Set<AnyCancellable> — alla prenumerationer annulleras vid rensning av setet. Detta är motsvarigheten till DisposeBag från RxSwift.

Måste jag lära mig Combine efter async/await?

Ja, Combine förblir relevant för strömmande data: UI-händelser, debounce, combineLatest, WebSocket. async/await är bekvämt för enstaka förfrågningar, Combine — för kontinuerliga eller flera flöden. Båda ramverken är komplementära — Publisher kan omvandlas till AsyncSequence.

Hur använder man Combine med UIKit?

UIKit har inga inbyggda Publishers, men Apple tillhandahåller tillägg: NotificationCenter.default.publisher(for:), Timer.publish, URLSession.dataTaskPublisher. För anpassade UI-händelser används PassthroughSubject eller @IBAction inslagna i Publisher via Future eller Subject.

Vad är backpressure i Combine?

Backpressure — en mekanism för att kontrollera flödeshastigheten: Subscriber informerar Publisher via Demand hur många element det är redo att bearbeta. Om Demand = .max(1), väntar Publisher på begäran innan det skickar nästa värde. Detta förhindrar buffer spill när producentens och konsumentens hastigheter inte matchar.

Sammanfattning

  • Combine — inbyggt reaktivt Apple-ramverk för iOS 13+ med Publisher-Subscriber-mönster och strikt feltypning
  • Publisher och Subscriber bildar en kommunikationskanal med Demand-hantering för backpressure
  • Subject (Passthrough och CurrentValue) integrerar imperativ kod i reaktiva kedjor
  • Operatorer map, flatMap, combineLatest, debounce, catch ger deklarativ flödesbearbetning
  • Schedulers via DispatchQueue och RunLoop hanterar exekveringstrådar utan att blockera UI
  • SwiftUI använder Combine via @Published, ObservableObject och @StateObject för reaktiva Vyer
  • RxSwift förblir alternativ för projekt med iOS 8+, Combine — val för nya projekt på iOS 13+

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också