Apple's new Combine framework
Apple's take on Reactive Programming: check this first introduction if you want to know more about this fundamental building block.
A first look at Apple’s take on Reactive Programming
This year’s WWDC really is a blast: all these new things like SwiftUI, Catalyst, RealityKit and more. But one new thing really didn't get much attention yet: the new library from Apple, implementing the Reactive Programming fundamentals called Combine.
In the coming days more will become clear, as most sessions related to Combine are still ahead of us. The documentation for Combine is already available at https://developer.apple.com/documentation/combine, but it does not provide a great tutorial or an intro to get to know the features. However, there are some clues. Let's go through the things that we do know!
Note that if you do not know what Reactive Programming is, it might be worthwhile to first take a look at the excellent documentation available about the paradigm. See for example https://www.reactivex.io, and https://github.com/ReactiveX/RxSwift.
Comparing RxSwift vs Combine
In RxSwift, creating new input Observables is very easy. For example, you can do this:
In Combine, the inputs are called Publishers and the async equivalent of above, can be created like this:
The interfaces used in these two snippets are a bit different. First of all, in Rx subscription results in a Disposable, which allows you to stop the subscription, Combine does not have this. Instead, if you do not want to receive further updates, you can just dereference the Publisher chain.
Furthermore, the return type of onNext in RxSwift is void: when passing data down the stream, no data bubbles back up. With Combine, calling `subscriber.receive` will return a `Int`, denoting how many updates the downstream can handle at this time. This is called backpressure, and this is something not available in RxSwift or RxJS. In the RP libaries with backpressure, backpressure will ‘applied’ if there is more data to be processed than can currently be processed downstream. Having or not having backpressure is an active design choice, and it is important to know that Combine has this.
Publishers
Publisher
is a protocol, so you can create any struct that implements it, but Apple also provides a whole set of pre-made Publishers in the Publishers
(plural) enum.
In above sample we created an async Publisher in the struct-way. An alternative would be:
We gave a function-block that performs the work. You can do anything you want inside this function, as long as you adhere to at some point calling the subscriber closure.
Other useful Publishers are:
Operators
Several operators — that we know from Rx — are available, for example:
I did not yet look through the full list, but it looks like most of the Rx operators are represented, and otherwise it should be pretty straightforward to implement them ourselves. I'm expecting some great community operators!
Subscribers
Getting the values out of the stream works with Subscribers, like for example .sink
:
That was easy! However, a more awesome example of the power of Combine is in the Subscribers.Assign subscriber. For example, see how we can dynamically assign the title field of this Article reference type:
That is some really awesome functionality!
Inter-op between RxSwift & Combine
Of course, the two Reactive frameworks can also live together. No need to convert you full codebase to Combine: if you want to put an Observable
in a SwiftUI, that is possible. This is how you could convert from Observable
to Publisher
:
The otherway-round should be possible too. Note that the demand
property is unused. RxSwift has no back-pressure, so when converting, you need to ensure that you only use Observables that you have already throttled and/or you know that wont overrun the UI system. It would also be possible to create some sort of dynamic operator that does the back-pressure by either dropping or buffering (these are the options in RxJava).
Wrap up
After a quick dive into this new framework, I'm already thrilled with the potential.
Interestingly Combine does not depend on Foundation types, in fact, it lies at a lower level than Foundation. Talking with Apple Engineers, it really sounds like they have optimized the hell out of this framework. Quoting an Apple engineer: “The memory models of RxSwift and Combine are very different. Combine is really made for performance.” Looking at the existing Publishers, Apple is already using this in high performance systems, like the RealityKit framework (see for example Scene.Publisher).
Compared to RxSwift, Combine has very different semantics. The lack of DisposeBags & the presence of back-pressure are probably the direct effects of Apple doubling down on performance & integrating Combine at other low-level visualization & AR systems. It is striking that the first FRP framework (by Conal Elliot) was made for visualizations, and used a frame-rate dependent system: with back-pressure, the RP system actually becomes more of a long-polling/sampling system than a full reactive framework like Rx.
In this first beta release, the set of Publishers and Sinks delivered by Apple is still small, but a little bird told us that upcoming releases will have even more convenient Publishers for common observable stuff like URLSession, NotificationCenter & KVO. I'm exited about using SwiftUI with this Combine framework, and hope you are too!
References
- https://developer.apple.com/documentation/combine
- https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift
Check out our other WWDC19 blog posts on https://engineering.q42.nl/tag/wwdc.