Combine
Customize handling of asynchronous events by combining event-processing operators.
The Combine framework provides a declarative Swift API for processing values over time. These values can represent many kinds of asynchronous events. Combine declares publishers to expose values that can change over time, and subscribers to receive those vlaues from the publishers.
A unified, declarative API for processing values over time
- Callbacks
- Closures
- Notifications
- Key Value Observing
Features
- Generic
- Type safe
- Composition first
- Request driven
Back pressure
Combine is designed such that the subscriber controls the flow data, and because of that it also controls what and when processing happens in the pipeline. This is a feature of Combine called back-pressure.
This means that the subscriber drives the processing within a pipeline by providing information about how much information it wants or can accept. When a subscriber is connected to a publisher, it requests data based on a specific Demand.
The demand request is propagated up through the composed pipeline. Each operator in turn accepts the request for data and in turn requests information from the publishers to which it is connected.
1 | In the first release of the Combine framework - in iOS 13 prior to iOS 13.3 and macOS prior to 10.15.2 - when the subscriber requested data with a Demand, that call itself was asynchronous. |
With the subscriber driving this process, it allows Combine to support cancellation. Subscribers all conform to the Cancellabele protocol. This means they all have a function cancel() that can be invoked to terminate a pipeline and stop all realted processing.
Lifecycle of Publishers and Subscribers
When the subscriber is attached to a publisher, it starts with a call to
.subscribe(_: Subscriber).The publisher in turn acknowledges the subscription calling
receive(subscription: Subscription).After the subscription has been acknowledged, the subscriber requests N values with
request(_: Demaind).The publisher may then (as it has values) send N(or fewer) values using
receive(_: Input). A publisher should never send more than the demand requested.Any time after the subscription has been acknowledged, the subscriber may send a
cancellationwith.cancel().A publisher may optionally send
completion:receive(completion:). A completion can be either a normal termination, or may be a.failurecompletion, optionally propagating an error type. A pipeline that has been cancelled will not send any completions.
Key Concepts
Publisher
The Publisher protocol declares a type that can deliver a sequence of values over time. Publishers have operators to act on the values received from upstream publishers and republish them.
- Defines how values and errors are produced
- Value type
- Allows registration of a
Subscriber
1 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) |
- Recipe for an event stream
- finite: could complete, e.g. network request
- infinite: failure or finish will never happen, e.g. button click
- Operators describe new publishers from existing
- Strongly typed values/errors over time
- Can be synchronous or asynchronous
- Can attach compatible Subscribers
Subscriber
At the end of a chain of publishers, a Subscriber acts on elements as it receives them. Publishers only emit values when explicitly requested to do so by subscribers. This puts your subscriber code in control of how fast it receives events from the publishers it’s connected to.
Receives values and a completion
Reference type
1 | protocol Subscriber { |
- Receives values and a completion
- Reference type
The Pattern
Subscriberis attached toPublisherPublishersends a SubscriptionSubscriberrequests N valuesPublishersends N values or lessPublishersends completion
Kinds of Subscribers
Key Path Assignment
1
2
3
4let trickNamePublisher = ... // Publisher of <String, Never>
let canceller = trickNamePublisher.assign(to: \.someProperty, on: someObject)
// ...
canceller.cancel()Sinks
1
2
3
4
5let trickNamePublisher = ... // Publisher of <String, Never>
let canceller = trickNamePublisher.sink { trickName in
// Do something with trickName
}Subjects
- Behave like both Publisher and Subscriber
Broadcast values to multiple subscribers
1
2
3
4protocol Subject: Publisher, AnyObject {
func send(_ value: Output)
func send(completion: Subscribers.Completion<Failure>)
}Kinds of Subjects
- Passthrough
- CurrentValue
SwiftUI
Operators
an object that acts both like a subscriber and a publisher. Operators are classes that adopt both the Subscriber protocol and Publisher protocol. They support subscribing to a publisher, and sending resutls to any subscribers.
- Adopts
Publisher - Describes a behavior for changing values
- Subscribes to a
Publisher(“upstream”) - Sends result to a
Subscriber(“downstream”) - Value type
- Declarative Operator API
- Functional transformations
- List operations
- Error handling
- Thread or queue movement
- Scheduling and time
Subject
1 | /// A publisher that exposes a method for outside callers to publish elements. |
Kinds of Subject
- PassthroughSubject
1 | /// A subject that broadcasts elements to downstream subscribers. |
- CurrentValueSubject
1 | /// A subject that wraps a single value and publishes a new element whenever the value changes. |
Scheduler
When
1 | /// A protocol that defines when and how to execute a closure. |
Where
1 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) |