WWDC 19
Introducing Combine
Combine doesn’t intend to replace notifications, KVO, target-action, delegates, etc.
Instead, it’s meant to be a common interface between them
Publisher: defines how values and errors are produced. (not necessarily the thing that actually produces them)
They’re simple descriptions, value types (structs)
Can be subscribed to by subscribers
Has two associated types: produced value and error type (use Never
if it never produces an error)
Subscriber: it’s what receives the values from the publisher
Receives a value + completion if the publisher is finite
Subscribers mutate some state when they receive a value, so they’re reference types (classes)
Subscriber has an input type and error type (they must match publisher’s output and error types)
Flow of subscription:
- 1. Subscriber calls
subscribe()
on Publisher - 2. Publisher calls
receive(subscription:)
- 3. Subscriber calls
request(_: Demand)
to indicate how many items they can receive - 4. Publisher sends those items via
receive(_: Input)
- 5. Publisher sends
receive(completion:)
when finished
NotificationCenter.Publisher(center: .default, name: .notificationName, object: obj)
Subscribers.Assign(object: obj, keyPath: \.address)
Operators:
They are both publishers and subscribers
They subscribe to a publisher (upstream), transform the received values and pass them on to subscribers (downstream)
Declarative, value types (structs)
p = Publishers.Map(upstream: somePublisher) { return … } p.subscribe(subscriber)
Operators can just be called on any publisher:
publisher.map { … }
Names of operators are generally taken from Swift standard library methods when possible (compactMap
, filter
, prefix
…)
And publishers can be created from various classes like this:
NotificationCenter.default.publisher(for: .notificationName, object: obj).map { … }
Some subscribers can be created this way too:
publisher.assign(to: \.path, on: obj)
This returns a “Cancellable”, which unsubscribes everything when released
Zip3(a, b, c).map { a, b, c in … }
→ waits for a new element from each stream and combines them into a tuple
CombineLatest3(a, b, c).map { a, b, c in … }
→ caches last value and sends an updated tuple whenever any of the values changes
It’s ok to adopt Combine incrementally, you don’t have to go all in
Good places to use:
- receiving
NotificationCenter
notifications withfilter()
- combining the result of several network operations with
zip()
- decoding JSON from a URL operation with
decode()