The Pub-Sub Pattern
The Publish-Subscribe Pattern in Software Architecture is a messaging pattern in which senders of messages, called Publishers do not send messages to specific receivers, and have no knowledge of the receivers, called Subscribers. Subscribers are free to subscribe to any publisher to listen to and receive messages that the publishers broadcast.
This forms the core concept behind the Combine Framework.
Combine
The fundamental building blocks that constitute Combine are:
- Publishers
- Subscribers
- Operators
Let’s go over each of this in a bit more detail.
Publishers
Publishers declare a type that can publish values over time. These are objects that can be subscribed to and define an asynchronous event stream. There are four kinds of messages Publishers are able to transmit:
- Subscription
A connection between a publisher and a subscriber.
- Value
The standard behaviour of a publisher we’re typically interested in, is its ability to publish useful data. These can be any kind of values that a subscriber might be designed to receive.
- Error
The Publisher could also transmit an error, when there’s one. A Subscriber could then respond accordingly when it encounters an error.
This is represented as:
1.failure(e)
- Completion
The completion is an optional signal that a publisher could transmit to indicate that the stream has ended successfully and that no more data will be transmitted.
This is represented as:1.finished
Both .failure
and .finished
are terminal messages which indicate that the stream is no longer transmitting messages. The subscribers are expected to be designed to handle these cases.
Publishers are typically described by two attributes:
- Output: The kind of values the published by the publishers.
- Failure: The kind of errors the publisher might publish
In Combine, Publishers are standard Swift protocols and the attributes that describe them are denoted using associated types —
1protocol Publisher {2 associatedtype Output3 associatedtype Failure45 func subscribe<S: Subscribe>(_ subscribe: S) {}6}
Publishers also describe how to attach subscribers to themselves as long as the subscribers Input and Failure type match the Publisher’s Output and Failure type. Which is understandable if we’re designing subscribers to listen to a certain kind of publisher.
So, in short any publisher can be denoted as –
1PublisherName<Output, Failure>
Subscribers
Subscribers declare a type that can receive an input from a publisher. They’re described by two attributes:
- Input: The kind of values it can receive
- Failure: The kind of errors it can receive
Describing a subscriber is again as simple as —
1SubscriberName<Input, Failure>
Subscribers have three key functions:
- Receive a subscription
- Receive an input (value from a publisher)
- Receive a terminal signal, completion (incase of finite publishers) or a Failure.
Subscribers act and mutate state on the values that they receive from the publishers, because of which they’re reference types by which I mean they’re classes.
Data Flow
A Publisher is responsible for transmitting data to a subscriber, but only after a subscription is obtained by the subscriber.
The pattern may usually include an operator in between which makes for two kinds of streams to be possible:
- Upstream: Stream of data from a publisher
- Downstream: Stream of data to a subscriber
If we recall from the previous post, the data flow between the publishers and subscribers is nothing but a stream of values —
With this in mind, let’s look at the pattern that’s used in establishing this communication.
The Pattern
Let’s look into how the communication between the publishers and subscribers take place in Combine —
The Pattern of Communication
It’s typically a 4 step process. The initial setup might consist of an object holding a subscriber which intends to listen to a publisher —
Step I: The object holding the subscriber calls the subscribe(_:)
method to request for a subscription.
Step II: The publisher then sends a subscription to the subscriber, and the subscriber is notified by[receive(subscription: Subscription)]
Step III: The Subscriber then sends in a demand request for n
values to the publisher.
Step IV: The Publisher now sends the n
or n-1
values to the subscriber.
If the Publisher is finite, after the values are sent, a completion signal is sent or a failure signal in the event of an error.