Some time ago Apple has introduced a brand new framework called Combine (WWDC 2019). This framework is aimed to reduce developers’ need to implement multiple delegate callbacks and completion handlers and simplify events handling in general. Also, Combine has a declarative API which dramatically improves the readability of the code.
Just imaging that you have a search field and would like to filter a list of items according to the entered text. You can use a UITextView delegate for that, of course, you’ll have to use a timer in addition to throttle user’s input and you’ll definitely have to process the entered text somehow to make it suitable for your filtering logic.
Or you would like to make a network request to your server API, then process this request and transform received data into the model, after that you’d like to filter that data and change your UI accordingly.
Looks pretty complex, isn’t it? So here comes Combine which can drastically simplify all this stuff and allow you to create a single processing chain for the process.
Generally, Combine is simple and consists of 3 concepts:
Let’s discuss each one to understand their purpose.
The publisher is a type that defines how values and errors are produced over time. The publisher must be structure and follow the Publisher protocol. Also, the publisher allows one or more subscribers to subscribe to the values it produces.
The publisher has two associated types, one for
Output and another one for
Failure, and method
receive. Here is how Publisher protocol looks like:
Receive method allows us to connect the publisher with one or more subscribers.
Subscriber is a class that is responsible for requesting end receiving data from the publisher, it can also receive an error. We can look at the subscriber as a final destination point of our data flow chain. Subscriber implements the Subscriber protocol which as Input and Failure associated types and 3 receive methods for different purposes. Here is how Subscriber protocol looks like:
receive(subscription: Subscription) method will be invoked by the publisher right after a connection between the publisher and subscriber is established (it happens exactly after calling
receive<S>(subscriber: S) method).
After that instance of
Subscription can be used to request data from the publisher, also we can use this instance to cancel the subscription and exit the flow.
Next, when new values are ready the publisher calls the second method of subscriber
receive(_ input: Self.Input) with new values.
When the publisher stops publishing it calls the last method
receive(completion: Subscribers.Completion<Self.Failure>) with one of the two enum values represents either success
.finished or error
We should keep in mind one important thing that Publisher and Subscriber must obey to be able to connect to each other:
Output of the publisher must be of the same type as
Input of the subscriber. Also,
Failure of both should be the same type or
Never if there can’t be any error in the flow.
The last important piece of the chain is Operators. Just imagine that publisher can emit numbers in form of a string, but the subscriber can accept only numbers in form of integers. So we have to convert the value somewhere in between. Here come operators in play.
Technically operators are convenience functions that allow receiving the data from the upstream publisher (or operator), transform it, and republish it down the stream to the next step in the chain.
The Data Flow in Combine
Every chain of data flow in Combine can be represented with the following scheme:
Publisher -> [Operator] -> [Operator] -> ... -> Subscriber(s)
This means that the value the
publisher emits can be processed via zero or more
operators and after that can be consumed by one or more
Here is the simple Counter class that uses PassthroughSubject and publisher to update its clients about count changes
And Counter usage will be the following
Try to figure out the result of this code! Actually, it’s the following:
It would be nice to understand one important thing about the invocation of
subject.eraseToAnyPublisher() inside the computed property. Each time you invoke
eraseToAnyPublisher() it creates a new publisher, but the subject itself still remembers all previous publishers that are created earlier.
Well, looks like Apple has created a really interesting thing called Combine. Reactive programming is very popular now and it definitely would be a good idea to adopt some of its concepts. Using Combine is one of them.