First steps in functional reactive programming in Swift with Apple Combine framework

One debate over the past year in the iOS ecosystem was the around functional reactive framework like RxSwift or ReactiveCocoa. This year at WWDC2019, Apple took position on it and released their own functional reactive programming framework, here is Combine.

Even if we are at an early stage of this new framework, after all it got introduced couple weeks ago, I wanted to introduce some of its concepts and show how to integrate it into your code with very simple example. However, I won’t cover any usage with SwiftUI.

You don’t need any functional reactive programming background to follow this post. It’s is based on Xcode 11 Beta, iOS 13.0 Beta.

Update July 27th - The post is now updated it for Xcode 11 Beta 4.

Update Sept 14th - The post is now updated it for Xcode 11 GM.

Combine Concept

Combine framework is divided in 3 main components:

  • Publisher: it triggers sequence of value or new state through its Output.
  • Subscriber: it subscribes to a Publisher to get notified of any changes made using its Input.
  • Operators: it represents the layer in between for any transformation or manipulation of data from Publisher output to a Subscriber input as middle steps.

Publisher

Publisher is responsible to emit sequence of values. It also handle different kind of messages:

  • subscription - that’s the first event emitted between publisher and subscriber
  • value - any kind of elements emitted
  • error - the sequence finished by an error triggered
  • completion - the sequence finished successfully.

Subscriber

On the other side, Subscriber can receive emitted elements from a Publisher.

  • receive(subscription:) notify it subscriber has successfully subscribed to publisher
  • receive(_:) for any new element emitted
  • receive(completion:) when sequence finished successfully.

Let’s see now how they works together.

Combine in practice

Let’s start with a simple example. I would like to detect when the application changes of state between background and foreground. To do so, we’ll use NotificationCenter for that.

Apple documentation mentioned that NotificationCenter is one of the Foundation types that will support with Combine, but as it’s not done available yet, let’s see how we can do our own.

Starting with the publisher, the element triggering the changes will be NotificationCenter, so we can extend it to create a publisher from it.

import Combine

extension NotificationCenter {
    
    struct NotificationPublisher: Combine.Publisher {
        // implementing Publisher protocol
        typealias Output = Notification
        typealias Failure = Never
        
        let center: NotificationCenter
        let name: Notification.Name
        let object: Any?
        
        func receive<S>(subscriber: S) where S : Subscriber, 
            NotificationPublisher.Failure == S.Failure, 
            NotificationPublisher.Output == S.Input {
            // letting subscriber know subscription started
            subscriber.receive(subscription: Subscriptions.empty)

            // observing notification, any new element would be forwarded to subscriber
            center.addObserver(forName: name, object: object, queue: nil) { (notification) in
                let _ = subscriber.receive(notification)
            }
        }
    }

    func publisher(for name: Notification.Name) -> NotificationPublisher {
        return Publisher(center: self, name: name, object: nil)
    }
}

So far, I only create a Publisher struct for the NotificationCenter matching Combine protocol. However, to keep it explicitly different of Combine.Publisher, I renamed it to NotificationPublisher after Xcode 11 Beta 4.

For any new elements subscribing to this publisher, it will create a notification observer that will forward the changes. In short we wrapping NotificationCenter with Combine framework.

I’ve also added a small method to make it more handy to use.

let backgroundPublisher = NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)

Our publisher is ready, let’s add a subscriber to it.

The simplest one might be using sinks to use a closure as subscription.

backgroundPublisher.sink { notification in 
    print("Entered in background")
}

Another way to get those changes is to use Subject protocol. Behaving as a Subscriber but also Publisher, it can capture changes and forward them. For that, I’m going to use PassthroughSubject, a subject that passes along values and completions.

let subject = PassthroughSubject<Notification, Never>()
backgroundPublisher.subscribe(subject)

let cancellable = subject.sink { _ in 
    print("Entered in background)
}

But what about operators?

That’s where it can get quite interesting. We actually can transform the values along the subscription to keep only what. Let’s see how

First let’s create two publishers foreach notification. I’ll add an operator to transform the event into a String message.

let backgroundPublisher = NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
    .map({ _ in "Did enter background" })
        
let foregroundPublisher = NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)
    .map({ _ in "Will enter foreground" })

Then, I’m going to merge both publisher using Publishers.merge to create on sequence of String coming from both to keep only one subscription.

Publishers.Merge(backgroundPublisher, foregroundPublisher)
    .sink(receiveValue: { message in 
        print(message)
    })

Here is my output in my console.

> Will enter foreground
> Did enter background
> Will enter foreground

I could have also chain it all together and avoid to have different instantiation.

NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification) // publisher
    .map({ _ in "Did enter background" }) // operator
    .sink(receiveValue: { print($0) }) // subscriber

In conclusion, we’ve seen how Publisher and Subscriber work, how to use Subject as middle ground element and how to transform and combine events using Combine operators. I didn’t cover yet how use Combine with SwiftUI but Combine on its own is already very promising.

Happy coding!

© 2023 Benoit Pasquier. All Rights Reserved
Author's picture

Benoit Pasquier

Software Engineer πŸ‡«πŸ‡·, writing about career development, mobile engineering and self-improvement

ShopBack πŸ’°

Singapore πŸ‡ΈπŸ‡¬