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 — ä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.
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 — 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.
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 — ä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 — ä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.
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:).
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.
| Kategori | Operator | Syfte |
|---|---|---|
| Transformation | map / tryMap / flatMap | Transformering av värden eller flöden |
| Filtrering | filter / compactMap / removeDuplicates | Urval eller rensning av värden |
| Kombination | combineLatest / zip / merge | Kombinera flera Publishers |
| Tidshantering | debounce / throttle / delay | Fördröjning och förtunning av händelser |
| Felhantering | catch / retry / replaceError | Återhämtning efter Failure |
| Demand-hantering | buffer / collect | Gruppering 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.
// 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.
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.
// 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.
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 — 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.
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 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.
| Egenskap | Combine | RxSwift |
|---|---|---|
| Utvecklare | Apple (inbyggt i SDK) | ReactiveX (community) |
| iOS-version | iOS 13+ | iOS 8+ |
| Feltyp | Generic Failure (Never för UI) | Error (vilken som helst) |
| UI-integration | @Published + SwiftUI | RxCocoa + UIKit |
| Operatorer | ~100 inbyggda | 400+ operatorer |
| Swift Concurrency | Via .values (async sequence) | Via bryggbibliotek |
Vanliga frågor
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).
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.
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.
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.
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
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.
Läs också