Roughly one month ago Apple showcased SwiftUI at the WWDC 2019. Since we humans can only focus on one new thing, all the hype was reserved for SwiftUI and people almost missed Apple’s second big announcement: Combine. A lot of folks were seeing Combine only as an enabler for SwiftUI, but Combine also introduces first-class reactive programming to the Apple platforms without the need for external dependencies like RxSwift or ReactiveSwift. In this article, we will take a deeper look at Combine and compare it to RxSwift, the current state of the art reactive framework for iOS programming.
What is Combine?
Before we start with the comparison, let’s take a look at how Apple answers the question of what Combine really is:
The Combine framework provides a declarative Swift API for processing values over time.
Hmm, that sounds familiar. Is that not the same as reactive programming? It’s funny to see Apple trying to avoid the word ‘reactive programming’. There is not a single mention of it in its documentation and neither was in their WWDC presentations. It seems like Apple doesn’t want to give any credits to the fantastic community that build around reactive programming. Combine itself even implements the reactive streams specification with some small adaptions. Ok, so now that we know that Combine is “just another” reactive framework, we are ready to move on and see what makes it a better or worse fit for our needs.
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.
We should use Combine for the same reasons as people use reactive programming already. It allows developers to easily express (asynchronous) data flows, automatically evaluates them and propagates data changes. Your complicated event-processing code becomes easier to read and maintain. Apples announcement will be a huge push for reactive programming in general. It will bring modern coding techniques from other platforms (like the web) to the Apple platforms. If you follow our blog, you will know that we at QuickBird Studios are big fans of functional reactive programming and therefore use RxSwift on iOS, next to RxJava on Android or Flow on Kotlin. Are we going to migrate all our RxSwift code to Combine? Not really, there are a lot of pros and cons for both RxSwift and Combine. We’ll compare RxSwift and Combine thoroughly in this article.
First of all, Combine follows mostly the same principles as RxSwift but uses a different naming scheme. Most operators just have a different name but actually have an equivalent in RxSwift. If you come from RxSwift and have trouble finding the corresponding Combine operation or component you can use this great cheat-sheet created by Shai Mishali. These are the two most important naming changes: Observable -> Publisher Observer -> Subscriber
Error types and error handling
As mentioned above, RxSwift’s `Observable` is the counterpart to Combine’s `Publisher`. They fulfill the same purpose. If we look at their protocol specification tough, we see one big difference: an additional `Error` Type. In Combine, every `Publisher` needs to define its error type. The same applies to ReactiveSwift as well. On the other hand, RxSwift’s `Observable` does not use an error type. It might throw any kind of error any time. RxSwift’s `Observable` is probably easier to use because you don’t need to think about what kind of errors can be thrown. However, this means you have to make sure to handle errors on your own. Your compiler won’t help you if you forget about it. Small hint: If your stream doesn’t throw any errors at all you can use the `Never` type. You can see the explicit error type of Combine as an additional layer of type safety which also adds some code overhead. You can achieve similar behavior by using a `Result` type in RxSwift (this adds an additional error type, but your stream won’t stop after throwing an error) or a having explicit streams for your errors. Exception throwing and handling are also a little different in Combine. Combine separates throwing and non-throwing calls strictly. For example, there is a separate tryMap function which is a map that can explicitly throw errors. Since you cannot define which errors can be thrown in Swift, the resulting Publisher just uses the general `Swift.Error` type for errors 😔
RxSwift is a pretty optimized piece of software and I rarely heard any complaints about performance when using it (correctly). But Combine sets new standards. It’s a performance beast! From the beginning, Combine was designed with performance in mind. It delivers huge performance benefits compared to RxSwift. Tests showed an average 40% more data passthrough using Combine. We ported the RxSwift performance test-suite to Combine and created detailed reports about performance differences. The test code and more details are available here. As a summary, Combine was faster in almost every test. These statistics show every test-method and its result. Lower is better.
One of the reasons for this huge performance difference is the actual implementation of both frameworks. RxSwift uses Swift as their main programming language and needs to open a lot of sinks under the hood. That costs a lot of performance. Combine on the other side is a closed source project that is not necessarily written in Swift but just exposes a Swift interface. Apple can use a lot of performance optimizations that aren’t available to other programmers out there.
One huge downside of Combine is its non-existing backward compatibility. Combine needs iOS 13 / macOS Catalina and its new runtime to work. There is no backward compatibility to earlier versions of their operating systems planned. On the contrary RxSwift runs on iOS 8 upwards and therefore probably on more iOS versions than most apps out there. This will be a huge selling point for the next 1-3 years (for some companies even longer) until they can safely exclude customers with older iOS versions.
Back-pressure? Never heard of it? Back-pressure allows components that are not able to consume items fast enough to communicate their situation to their upstream components. This allows them to handle this situation and reduce the load for the consuming components. This prevents components under stress to drop messages in an uncontrolled fashion or to fail catastrophically. RxJava offers the `Flowable` type for exactly that purpose. RxSwift doesn’t have this type or any alternative and therefore doesn’t support back-pressure at all. Combine on the other side supports back-pressure out of the box.
Available components and operators
Combine can’t offer you the same amount of components and operators (see cheat-sheet mentioned before) as RxSwift does. Already the list of available traits is much shorter in Combine. But is this necessarily bad? I don’t think so. Combine tries to focus on the core components of reactive programming and excludes operators and components you don’t need that often. This makes it easier to understand the framework and gets rid of a lot of unnecessary complexity. And I’m pretty sure someone will implement the missing components and operators in an extension framework in the foreseeable future 😉
To use reactive programming in your app development workflow you need some way to bind your reactive streams to your UI and vice versa. RxCocoa is the go-to solution for RxSwift. It allows you to bind reactive streams to your views and get streams from them (e.g. for button taps, text changes, etc.). Combine doesn’t have such a first-party framework to connect it to Cocoa/CocoaTouch. Such an adapter framework is currently under development by the amazing freak4pc. Otherwise you can use the
assign method of Combine to bind a stream to a key path and therefore connect it to the properties of a view, but there is no way to get any stream from UI components. Besides that, SwiftUI is strongly connected to Combine and interacts pretty well with it. But SwiftUI it is a completely new UI framework and won’t be the go-to solution in the upcoming months. Since both of them rely on iOS 13 they will be introduced to most code bases together. It will take a few years until they both get introduced into code bases.
As you can see there aren’t that many huge differences between RxSwift and Combine. Both frameworks allow you to use reactive programming on the Apple platforms. The funny thing: the fact that Apple published combine might actually boost the popularity of RxSwift much more in the next years. RxSwift does not need to be afraid since Combine still lacks backward compatibility. This joy might only be temporary though. Users will eventually adopt to new iOS versions. Since Combine and RxSwift are so similar, at one point, more and more people might jump on Combine. In the mean time: keep using RxSwift and enjoy the happy life of reactive programming 🤓