Declarative UIKit, MVVM, DI, Combine, PropertyWrappers — Part 3

Andrii Petrovskyi
7 min readDec 4, 2021

PropertyWrappers + Combine

level: Intermediate

StarterProject

Part-1

Part-2

Some info:

PropertWrappers:
A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines a property.

Link to get more information

For example:
We have some String which has a dynamic part of text and some static, for example we have some money property whose value looks like “10 $” , so 10 is the dynamic part and $ is static. We can implement this in different ways but I want to propose you to implement this by using PropertyWrappers, it’s not a problem if we have only one object which must worked with this value, but if we have a 10 different objects, so we need to define our solution in each of them, but we can implement one property instead:

Combine:

Let’s try to understand what Combine is?

Apple tells us next:

“The Combine framework provides a declarative Swift API for processing values over time. These values can represent many kinds of asynchronous events. Combine declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers.”

It looks like Combine is nothing but “Reactive Programming”. But Apple doesn’t wont to use this word, to don’t give any credits to the fantastic community that build around reactive programming.

For the beginning some info about Reactive programming:

In computing, reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. With this paradigm, it’s possible to express static (e.g., arrays) or dynamic (e.g., event emitters) data streams with ease, and also communicate that an inferred dependency within the associated execution model exists, which facilitates the automatic propagation of the changed data flow

Let’s speak about: what Reactive and asynchronous is, in words

On the practice this looks like this:
Basically we need two entities, called Publisher and Listener, or in our case Publisher and Subscriber, also Combine provides another abstract entity and it called Operator.
When Publisher finish some operation he notifies all his Subscribers.
In words:
I want to eat, and i’m going to restaurant, so in this case for me kitchen in this restaurant is the Publisher, the waiter(Operator) and I’m a Subscriber. What happens when i chose the food? I’m calling the waiter, he takes my order and sends it to the kitchen. At this point we should try to understand what is the difference between asynchronous and synchronous.

Asynchronously means that something can work without blocking other processes. In our case it means that when we ordered, the restaurant doesn’t stop any process while our order will be ready, the waiter can take other orders, the same with the kitchen, and for us it means that we can do some other stuff. From the other side let’s imagine what if restaurant works synchronously, it means next: — when we ordered, our waiter stops any other processes, while our order will be ready, so no one can get in, no one can do the order, etc ..
So, hope deal with it.
When our delicious will be ready, we’ll get it from the waiter, it means that the kitchen(Publisher) has completed working on our task and we(Subscriber) got our order.

Or another example is Instagram:
When we(Subscriber) are subscribing one someone’s profile(Publisher), it means that we will get notifications when this user does some posts. That is how working Combine in real life)

So let’s back to programming, and dive dipper in Publishers, Subscribers and Operators.

Publisher is a type that can deliver a sequence of values over time. Publishers have operators to act on the values received from upstream publishers and republish them. Also we need to know that Publisher contains two associated types: Output(The kind of values published by this publisher) and Failure: Error(The kind of errors this publisher might publish.)

Subscriber is an instance that receives a stream of elements from a Publisher, along with life cycle events describing changes to their relationship. A given subscriber’s Input and Failure associated types must match the Output and Failure of its corresponding publisher.

Operator is a function that Attaches the specified subscriber to this publisher and tells the subscriber that the publisher has produced an element. So we can say them operator must contain two streams with Input and Failure of Subscriber and with Output and Failure of Publisher

If we go back to an example with a restaurant and for example I ordered some salad, in this case for the kitchen as for Publisher Output must be some salad and Failure could be the ingredients’ outfit, or some technical issues in one word some Error. And for me as for Subscriber the Input must be the salad and Failure must be the ingredients’ outfit, or some technical issues. Easy write?

Combine declared a few basic Publishers:
Future- A publisher that eventually produces a single value and then finishes or fails.

Just- A publisher that emits an output to each subscriber just once, and then finishes.

Deffered- A publisher that awaits subscription before running the supplied closure to create a publisher for the new subscriber.

Empty- A publisher that never publishes any values, and optionally finishes immediately.
Failure- A publisher that immediately terminates with the specified error.
Record- A publisher that allows for recording a series of inputs and a completion, for later playback to each subscriber.

And here is some info about Operators:
Publisher’s stream:

Combine provides the following subscribers as operators on the Publisher type:

sink(receiveCompletion:receiveValue:) executes arbitrary closures when it receives a completion signal and each time it receives a new element.

assign(to:on:) writes each newly-received value to a property identified by a key path on a given instance.

Subscriber’s Stream

func receive<S>(subscriber: S)

Attaches the specified subscriber to this publisher.

func subscribe<S>(S)

Attaches the specified subscriber to this publisher.

func subscribe<S>(S) -> AnyCancellable

Attaches the specified subject to this publisher.

So why i did called our waiter an Operator, because we can imagine him as Operator with two streams and it will looks like this:
Waiter<Input: Salad, Failure: KitchenError>,< Output:Salad, Failure: KitchenError>

So, hope at this point i’v got some imagine about Combine and how it works, to dive dipper here is some useful links:

1. Documentation
2. Combine from 0… part 1
3. Combine from 0… part 2

Why should we use Combine?

By adopting Combine, you’ll make your code easier to read and maintain, by centralizing your event-processing code and eliminating troublesome techniques like nested closures and convention-based callbacks”

That is what we actually will be doing with our project!

Another great feature of Combine is the great performance because it is a closed source project that is not necessarily written in Swift but just exposes a Swift interface.

But Combine has no backward compatibility. Combine needs iOS 13 / macOS Catalina and its new runtime to work.

After it we finally can start refactoring our project with Combine.
TODO List:
1. Import Combine
2. Change ImageViewModel’s closures with Combine Publishers
3. Update ImageViewController with Combine Subscribers

Let’s start!
At the beginning I need to import Combine to ImageViewModel, then update my protocols, and finally remove all closures, that all is only for ViewModel.
First problem which you can face with is that we can’t declare PropertyWrappers inside protocol, what??
But to work with Combine I need to use at least @Publisher wrapper, and I want to work on abstractions!
Don’t worry, I have some solution!

Let’s start from implementing our changes to ViewModel as if we don’t know about this problem!
First of all I want to update my ViewModel’s abstraction like this:

And here we go :(
Why can’t we declare ProppertyWrappers inside the protocols?

This is because property wrapper declarations get synthesized into three separate properties at compile-time, and this would not be appropriate for an abstract type.

But to avoid this problem we can create individual property requirements for each of the synthesized properties of a property wrapper, for example:

Instead of @Published in protocol we should use
var property: Published<Type>.Publisher:

But of course we need @Published inside ViewModel, the full implementation will look like this:

As you can see, our properties ($wrappedInProgress, $wrappedImageDataLoaded and $wrappedOnFailure) have been synthesized by the property wrapper on the concrete type, and we can simply return these from computed properties required by our protocol.

Then we can update our fetchImage method:

As you can see inside ImageViewModel we will be using wrapped properties, and each time when wrappedValue did changed, our computed Publishers will notify the Subscribers

Next step is to update our ImageViewController.

One important thing! Publishers and Subscribers keeps strong references on each other, it is very important to take this into account when implementing.

To cancel all subscriptions our Subscribers confirm to AnyCancellable protocol, so we need some Set of AnyCancellables, which will keep all our subscribers in one place, and to give us an opportunity to destroy relations when we need it.
Let’s implement:

First we need to import Combine inside ImageViewController file, and add new property
private var subscriptions = Set<AnyCancellable>()
right after viewModel property:

Next step is to prepare all our subscribers, so I prefer to separate each of them in different methods, and then we can append them into our subscriptions.

So our bindToViewModel. and subscriptions will look like this:

And here is our ImageViewController at this step:

Thank you for reading, in the next part i’ll show you how to implement Dependency Injection using PropertyWrappers!

--

--