Combine Getting Started Guide
Learn Swift’s Combine framework through examples and sample code. A brief intro to publishers, operators, and subscribers.
What is Combine?
In short, Combine enables subscribing to output or values over time with a declarative API. For example, if you’re making a network request to fetch JSON.
Notice how Combine is built into url session, you’ll see it throughout Apple’s API ecosystem i.e notification center, timers.
- Create a new data task publisher to fetch data at a URL.
- Map on the response and get the
- Decode the response into a dictionary.
- CompactMap on the dictionary to the property results.
- Replace errors with an empty array (throwing away the error).
- Create a new publisher on the main queue.
- Assign the subscription input to the text property on
- Cancel the subscription and release it from memory.
We’ve achieved a composable chain of operations in a URL as an input and delivered the output to subscribers in a UI consumable model.
Another way to remember this pipeline is with an analogy using Netflix. Netflix publishes movies and tv shows to their servers. Then some operations are run to make them appear in an app. Finally, you subscribe to Netflix to access the movies, and when you no longer want to use Netflix, you cancel your subscription.
Combine has four main parts, publishers, operators, subscribers, and cancellable. Get familiar with these four terms because they are the foundation of the framework. Here is a cheatsheet of the terminology.
* Outputs values over time, allows 0 or more values or errors
to be emitted.
* PassthroughSubject, CurrentValueSubject, @Published, JustOperators
* Input and output same or new publisher
* map, tryMap, compactMap, filter, decode, scan, receive(on:), ...Subscriber
* Inputs values and a completion/error
* sink(_:), assign(on:to:), onReceive(_:perform:)Cancellable
* Cancels the subscription and releases memory
* store(in: &cancellables) // Set<AnyCancellable>
Publishers output 0 or more values once a subscriber has subscribed. In addition to ones built into Apple's APIs, you can create your own. The common publishers used are
PassthroughSubject sends a stream of values to subscribers. Under the hood, you can see that it is generic over some
Error, then returns a
cancellable (more on that later).
Notice in the example above the second generic parameter
Never, meaning it will never emit an error. However, if we want to handle an error, it make it a
CurrentValueSubjects is similar to a
PassthroughSubjects but can be initialized with a default value. Another difference is we can access the
value property on it to fetch the publisher's current value at that moment in time.
In the example above, we are simulating the search bar UX, and anytime the user types on the keyboard, a new string is published, and an API request is made.
Published is a property wrapper that can only be used in classes. Unlike the subjects above,
Published they can only send new values on assignment, not via a
send() method. To subscribe to a
Published property, you can use the $ operator. Otherwise, you will get a compiler error.
⚠️ Publishing occurs in the property’s
willSetblock, meaning subscribers receive the new value before it’s actually set on the property — Apple Docs
Here are a few others from the Apple Combine documentation that I haven’t had to use too often, but they may be helpful in a particular scenario.
class FutureA publisher that eventually produces a single value and then finishes or fails.struct JustA publisher that emits an output to each subscriber just once, and then finishes.struct EmptyA publisher that never publishes any values, and optionally finishes immediately.struct FailA publisher that immediately terminates with the specified error.
Operators take in some input and return the same or new publisher while deterministically defining an order or chain of operations. You can combine many operators because they are extremely decoupled.
There are too many operators to go over, and you’ve already seen a few in action above. For a full list, visit this free book resource here.
I do want to go over the difference between
receive(on:). It’s easy to get these two mixed up or not know the right one to use.
subscribe(on:)changes the queue for the operations and subscription to the background.
receive(on:)changes the queue for the downstream subscription back to the main queue.
⚠️ Notice the
print()operators. You can use these to easily debug errors or inspect what is happening in publishers.
Subscribers receive input from publishers after any operators have transformed it. In addition to receiving the output value, subscribers also can receive a completion or error, meaning the publisher has stopped emitting values. There are two ways to subscribe to a publisher in Combine.
Sink is an operator that subscribes to a publisher. Depending on your publisher, the signature can vary. For example, if the publisher and give you an
Errorthe signature of
sink will require adding a completion block where you can handle it. Otherwise, you can use the short-hand syntax below.
We’ve seen an example of
assign(to:on:)above already. In short, it assigns the output value from the publisher to a keypath property (kvo compliant) on an NSObject. It has an alternative initializer
assign(to:) that can be used to assign the subscription to another publisher.
SwiftUI has a built-in modifier that acts as a subscriber. It takes a publisher as the first argument and gives you a block that is called when a value is published.
In short, a Cancellable cancels the subscription and releases the publisher from memory. Cancellable is a protocol that every publisher, operator, and subscriber conform to and return.
AnyCancellable is the concrete type used to discard the subscription from memory or bind to the subscription's lifespan.
⚠️ Subscribers conform to Cancellable, meaning they have a function called
cancel(). Don’t call it on the subscription directly as it doesn’t discard it from memory, use the approaches below.
Anytime you are capturing
self in a subscription, like
sink, be sure to do it weakly (
[weak self]). Capturing
self in a subscription block, can prevent the cancellable from removing the reference on
Note you can’t mutate a property in a SwiftUI struct without using state. So storing or assigning a publisher to a cancellable will cause a compiler error. It’s best to move any publishers out of SwiftUI views and into a view model where the view model can manage the publisher lifecycle. Then use the property wrapper
@ObservedObject in the view and update the UI as properties on the view model change.
As you can see, Combine is a powerful framework. We’ve gone over the major parts of it, like publishers, operators, and subscribers. With these new concepts, you are more than equipped to start it. The best part of using Combine is the APIs don’t need to be maintained by you, and the code is extremely composable and reusable. When testing it, you only need to worry about the input and output, not the complex stuff in the middle.
Few resources if you want to go more in-depth (no aff):