It was really exciting to hear about Apples new framework called Combine at WWDC 2019. Finally, we have a native way to write functional reactive code and to build apps in a declarative way.
The main components of Combine are Publisher, Subject, Subscriber and Operator. Here is a brief summary of what they do:
exposes values of a certain type over time
can be completed or optionally fail with an error
a mutable publisher
has the ability to send new values after it's initialization
there are two subject types available:
CurrentValueSubject - as the name indicates, this subject type has access to the current value
PassthroughSubject - as the name indicates, this subject passes the current value through, i.e. it has no access to it
receives values from publishers / subjects
modifies values that are send from publishers / subjects
In the following figure, we can see these components in action. We will go through the example step by step and in more detail below.
Imagine, we have an app that presents articles to the user. The articles can receive likes. As a requirement, we want to be able to change the count of likes for every article and also to notify subscribers about this change.
Publishers & Subjects
As the first step, we use a combination of Combine's publisher and subject to achieve our goal.
We define a subject named likesCountSubject as a private variable of type CurrentValueSubject<Int, Never>. We will use this subject to send new values to subscribers. Since the subject is a generic type we specify it's Output and Failure type. The output type defines what kind of values the subject will send, in our case Int values. Since in cannot fail in our example, we use Never as the error type.
We define the init method with a likesCount parameter to give likesCountSubject an initial value.
We define a publisher named likesCountPublisher of type AnyPublisher<Int, Never>. It has the same Output and Failure type as our subject. The publisher can be used by subscribers. We don't necessarily need the publisher here, we could simply make our subject public. But since we don't want anybody else outside of the article struct sending new values, we use a publisher to only make the subscribing part available to the outside world.
We define the addLike method for increasing the count of likes. Here, we use our subject to send new values. To be able to add a like we access the subject's value property to get the current value of likes. This is the reason why we used a CurrentValueSubject and not a PassthroughSubject in this case, since only a CurrentValueSubject has access to it's current value.
We create an article and give it an initial likes count of 5.
We create a subscriber named likesCountSubscriber which is interested in any update on the likes count. The subscriber uses the publisher and it's sink(receiveValue:) method to subscribe for updates. Now, every time the likes count changes, the closure with be called that prints the new value.
We increase the count of likes by calling addLike() twice.
Therefore, the code produces the following output:
When we create a new subscriber, the publisher always returns an object that conforms to the Cancellable protocol. So if we wanted to cancel receiving new values, we could call the cancel() method on the subscriber.
For example, if we add the cancel call in between increasing likes,
Now, we can use operators to modify the received values before the printing closure is executed. For example, if we want to print something more descriptive, we can use the map operator to map the Int values to String values:
The lifecycle of a subscriber is linked to the lifecycle of the retaining object. Whenever this object is released, the cancel method is automatically called on the subscriber property and it will be released as well.
Of course, just like with "traditional" memory management in Swift, you need to be aware of not creating retain cycles, e.g. when using strong selfs in the sink closure.
Now that you know the Combine basics, my suggestion for the next step would be to look into property wrappers. Especially interesting in this case is the @Publish property wrapper that turns a variable into a Combine publisher.