MacKuba

Kuba Suder's blog on Mac & iOS development

Introducing Combine

Categories: Foundation, WWDC 19 0 comments Watch the video

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. 1. Subscriber calls subscribe() on Publisher
  2. 2. Publisher calls receive(subscription:)
  3. 3. Subscriber calls request(_: Demand) to indicate how many items they can receive
  4. 4. Publisher sends those items via receive(_: Input)
  5. 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 with filter()
  • combining the result of several network operations with zip()
  • decoding JSON from a URL operation with decode()