Swift/Combine: Setting up Services

Image for post
Image for post

In this article I’ll present a solution what we worked out to start required services that depend on each other. While this is not very spectactular and solutions are available, we focused on setting this up using the new Combine features, especially Futures and Promises.

The example I will use consists of a CloudService, checking if iCloud is available, and a DatabaseService, that sets up a CoreData storage depending on the iCloud availability.

Be warned: The story will take some of your time to read and understand. It reflects my current understanding, so it is possible that there are things that a more experienced programmer might optimize. However, I believe that it may help you to enter this kind of reactive solutions with the relatively new Combine framework.

What’s the basic idea behind the approach?

  • The AppDelegate shall be kept as clean as possible.
  • There shall be a method how the services can be accessed from anywhere. This allows, for instance, to have the service check the availability of iCloud in case of changes, and to have some consumer react on that change.

So, the first idea was to set up a ServiceRegister where the required services can be registered at. To allow easy access lateron, there shall be an enum that provides the keys for a Dictionary that stores the services.

The AppDelegate part
The ServiceRegister
The Service protocol
The ServiceType enumeration

So simple the idea, so non-working is it. The problem is that the protocol has associated types, so the compiler complains:

We can solve this problem by wrapping the protocol into a struct:

To have this generic struct as value for the dictionary, we have to change its value type:

The registration function changes as well:

Calling the registration now looks like this:

So, now we can register our services conforming to the Service protocol, wrapped by the AnyService structure. And we can start the services by just calling startServices() of the ServiceRegister.

But: How shall that startServices() shall work? We want to achieve two goals:

  1. Have the service work in the background, as it may take some time for them to process, and they shall not block the main task.
  2. Have a handling for dependencies between the services; in our case: have the database service wait for the cloud service deliver the cloud status.

To achieve this, we can use Combine’s Future . The CloudService ‘s start function is then defined like this:

The start() function returns a Future with a promise. In the ServiceRegister ‘s startServices() function we can take this promise and react on its result, when they are available:

First, we get the cloud service from the registry. With the .start() operator we trigger its execution.

The .sink lets us react on the result and has two parts:

  • The receiveCompletion delivers the overall status, especially errors, where we can react on by checking .failure .
  • The receiveValue delivers the value in case of successful execution. In our case, we want to set a property with the iCloud’s availability status.

This makes the code pretty clean, but when you’re used to classic programming paradigms, like me, this is kind of unusual thinking. The major advantage I see is that we’re getting rid of closures, and we can let errors “bubble up” until we really want to handle them.

But we’re not yet finished. We still have to start the database service, depending on the iCloud status.

What we do is set up the DatabaseService in a similar way (I will not show that here, as it’s pretty similar to the code above).

Given that, we can enhance the startServices() function:

If we have received a value from the cloudService , we’re taking the databaseService and perform the same operations as before on it. The result here is that we provide a databaseStorageType to a property of the ServiceRegister , so that we can access that from somewhere else.

Actually, we’re done.

Just one more thing: You might have noticed the .store(in: &self.cancellables) statements. Why do we need those?

Well, I stumbled about the fact that the services started, but they never delivered a result. After some googling, I found out that the promise was not kept alive long enough.

You can enforce that by adding a property to your class using promises:

private var cancellables = Set<AnyCancellable>()

You then just have to add the above .store operation at the very end of the chain, and you’re done.

Quite some stuff to read and understand — I hope you enjoyed it and you can take some advantage of that. If you have any comments or ideas, please let me know. And perhaps you may want to clap — I’d really appreciate that!

Helping companies and humans to evolve, and trying to be agile in an own product development.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store