Reactive Programming in iOS with Combine

Feb 4 2021 · Swift 5.3, macOS 11.0, Xcode 12.2

Part 1: Getting Started

02. Publishers and Subscribers

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 01. Introduction Next episode: 03. Subscriber Operators and Subjects

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 02. Publishers and Subscribers

Now that you’ve learned some of the basic concepts of Combine, it’s time to jump in and learn about two of Combine’s core components: publishers and subscribers.

At the heart of Combine is the Publisher. It publishes or emits events that can include values of interest. If you’ve developed on Apple platforms before, you can think of a publisher as kind of like NotificationCenter. What observes a publisher in Combine is referred to as a…

Subscriber. It attaches to, or subscribes to a publisher — imagine that — and it can then receive the values and events the publisher emits.

The whole process goes like this:

A subscriber indicates that it wants to subscribe to a publisher.

The publisher then issues a subscription to the subscriber.

Now the subscriber can request values from the publisher.

And the publisher can oblige by sending values to the subscriber.

A publisher can continue to send values…

…until it sends a completion event to the subscriber, to indicate that it won’t send any more values. This is called a marble diagram and the vertical bar represents a normal “I’m finished” completion event.

A publisher can also complete with a failure event, which is shown as a red X in a marble diagram. Just remember that once a publisher emits a completion event, it’s done and can no longer emit any more values or another completion event.

The requirements of a publisher and subscriber are defined in protocols. Here’s the publisher protocol and one of its most crucial extensions. The publisher protocol defines:

an Output associated type for the types of values a publisher will produce, and an associated type for the type of error it may emit, or Never if it is guaranteed to never emit an error.

A subscriber calls subscribe on a publisher to attach to it,

and the implementation of subscribe will call receive(subscriber:) to actually create the subscription.

Notice that subscribe and receive must line up the subscriber output and error types. So that takes care of the Publisher.

Now take a look at the Subscriber protocol.

…it defines an Input associated type for the type of values the subscriber will receive. This type must match the Output from the publisher.

It also defines a Failure type, or Never if there can’t be errors, just like in the Publisher protocol. If there is a Failure type, it must match the publisher’s Failure type.

The publisher calls receive(subscription:) on the subscriber to give it the subscription.

And the publisher calls receive on the subscriber to send it a new value.

When it’s done, the publisher calls receive(completion:) on the subscriber to tell it that it has finished producing values — either normally or due to an error.

The curious of you may be wondering about that CustomCombineIdentifierConvertible. This is just a protocol that defines a combineIdentifier requirement, which is used to uniquely identify each publisher stream, along with a default implementation.

There’s one more protocol to take a look at for now. Don’t worry, it’s a short one.

The Subscription protocol defines the connection between the publisher and the subscriber. A subscriber calls request to indicate it’s willing to receive more values, up to a max number. Or it can specify that it’s willing to receive an unlimited number of values.

This concept of a subscriber stating how many values it’s willing to receive is known as backpressure management. Without it, a subscriber could get flooded with more values from the publisher than it can handle. And this can lead to problems. You’ll learn more about managing backpressure in a later episode.

So what’s up with that Cancellable in the protocol definition?

Cancellable defines just a single method: cancel. Subscriptions return a cancelable type as a “cancellation token,” and can manually call cancel on it to cancel the subscription.

That’s a lot to take in, so no worries if all this doesn’t make total sense just yet. It soon will. How about jumping into some code?

Here in the Beginning playground for this episode, there are two things already defined for you: a subscriptions set. This is where you’ll store your subscriptions. Notice that it’s a set of AnyCancellable. AnyCancellable is just a concrete implementation of the Cancellable protocol.

You learned a moment ago that you can manually call cancel on a subscription to cancel it. However you can also add these tokens to a collection and they’ll be automatically canceled when that collection is about to be deallocated.

There’s one more thing I want to show you before you get to writing some code. Open Sources in the Project navigator and select SupportCode.swift. It contains a helper function example(of:).

You’ll use this function to wrap each example you’ll code throughout this course. Ok, back to the main playground page.

I mentioned earlier that you can think of a publisher as kind of like notification center. Actually, NotificationCenter has a Combine method that can broadcast notifications.

To see this in action, add a new example to your playground. First you get a handle to the default notification center, then create a notification name, and a publisher for your notification.

example(of: "NotificationCenter") {
  let center = NotificationCenter.default
  let myNotification = Notification.Name("MyNotification")

  let publisher = center
    .publisher(for: myNotification, object: nil)
}

Option-click on publisher(for:object:), and you’ll see that it returns a Publisher that emits an event when the notification center broadcasts a notification.

So what’s the point of publishing notifications when a notification center is already capable of broadcasting its notifications without a publisher? Glad you asked!

You can think of these types of methods as a bridge from the old to the new — a way to Combine-ify existing APIs such as NotificationCenter. Ok, let’s finish off this example.

First create a subscription to the publisher, then post a notification, and finally, cancel the subscription.

let subscription = publisher
  .sink { _ in
    print("Notification received from a publisher!")
  }

center.post(name: myNotification, object: nil)
subscription.cancel()

Run the playground and you’ll see the following:

——— Example of: Publisher ———
Notification received from a publisher!

You created a subscription by calling sink on the publisher — but don’t let the obscurity of that method name give you a sinking feeling. Option-click on sink and you’ll see that it simply provides an easy way to attach a subscriber with closures to handle the output of a publisher.

In this example, you ignore those closures and instead just print a message to indicate that a notification was received.

Remember that handsome graphic at the beginning of this video that showed all that back and forth between a publisher and a subscriber? Want to see that interplay in your actual code?

Of course you do! And it’s super easy to do. Just insert the print operator right before the sink.

.print()

Remember that operators are just methods declared on a publisher. Re-run the playground and check it out. print logs every publishing event so you can see exactly what’s going on.

——— Example of: NotificationCenter ———
receive subscription: (NotificationCenter Observer)
request unlimited
receive value: (name = MyNotification, object = nil, userInfo = nil)
Notification received from a publisher!
receive cancel

The subscription is created and received by the subscriber, it requests unlimited values, it receives the notification and prints out the message, and then the subscription is canceled. Pretty cool, right?

So, about that unlimited bit. The sink operator will continue to receive as many values as the publisher emits. This is known as unlimited demand, which you’ll learn more about shortly.