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 publisherreceive(_:)
for any new element emittedreceive(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!