ReactiveCocoa vs RxSwift
A detailed comparison of the two most popular Functional Reactive Programming frameworks for Swift: ReactiveCocoa vs RxSwift! By Colin Eberhardt.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
RxSwift
Microsoft’s ReactiveExtensions inspired many other frameworks that brought FRP concepts to JavaScript, Java, Scala and many other languages. This eventually lead to the formation of ReactiveX, a group which created a common API for FRP implementations; this allowed the various framework authors to work together. As a result, a developer familiar with Scala’s RxScala should find it relatively easy to transition to the Java equivalent, RxJava.
RxSwift is a relatively recent addition to ReactiveX, and thus currently lacks the popularity of ReactiveCocoa (about 4,000 stars on GitHub at the time of writing). However, the fact that RxSwift is part of ReactiveX will no doubt contribute to its popularity and longevity.
It’s interesting to note that both RxSwift and ReactiveCocoa share a common ancestor in ReactiveExtensions!
RxSwift vs. ReactiveCocoa
It’s time to dig into the details. RxSwift and ReactiveCocoa handle several aspects of FRP differently, so let’s take a look at a few of them.
Hot vs. Cold Signals
Imagine that you need to make a network request, parse the response and show it to the user:
let requestFlow = networkRequest.flatMap(parseResponse)
requestFlow.startWithNext {[weak self] result in
self?.showResult(result)
}
The network request will be initiated when you subscribe to the signal (when you use startWithNext
). These signals are called cold, because, as you might have guessed, they are in a “frozen” state until you actually subscribe to them.
On the other hand are hot signals. When you subscribe to one, it might already have started, so you might be observing the third or fourth event. The canonical example would be tapping on a keyboard. It doesn’t really make sense to “start” the tapping, like it makes for a server request.
Let’s recap:
- A cold signal is a piece of work you start when you subscribe to it. Each new subscriber starts that work. Subscribing to the
requestFlow
three times means making three network requests. - A hot signal can already be sending events. New subscribers don’t start it. Normally UI interactions are hot signals.
ReactiveCocoa provides types for both hot and cold signals: Signal<T, E>
and SignalProducer<T, E>
, respectively. RxSwift, however, has a single type called Observable<T>
which caters to both.
Does having different types to represent hot and cold signals matter?
Personally, I find that knowing the signal’s semantics is important, because it better describes how it is used in a specific context. When dealing with complex systems, that can make a big difference.
Independently of having different types or not, knowing about hot and cold signals is extremely important. As André Staltz puts it:
If you assume you are dealing with a hot signal and it turns out to be a cold one, you will be starting side effects for each new subscriber. This can have tremendous effects in your application. A common example, would be three or four entities in your app wanting to observe a network request and for each new subscription a different request would be started.
+1 point for ReactiveCocoa!
Error Handling
Before talking about error handling, let’s briefly recap the nature of the events that are dispatched in RxSwift and ReactiveCocoa. In both frameworks, there are three main events:
-
Next<T>
: This event is sent every time a new value (of typeT
) is pushed into the stream of events. In the locator example, theT
would be aCLLocation
. -
Completed
: Indicates that the stream of events has ended. After this event, noNext<T>
orError<E>
is sent. -
Error
: Indicates an error. In the server request example, this event would be sent if you had a server error. TheE
represents a type that conforms with theErrorType
protocol. After this event, noNext
orCompleted
is sent.
You might have noticed in the section about hot and cold signals that ReactiveCocoa’s Signal<T, E>
and SignalProducer<T, E>
have two parameterized types, while RxSwift’s Observable<T>
has one. The second type (E
) refers to a type that complies with the ErrorType
protocol. In RxSwift the type is omitted and instead treated internally as a type that complies with ErrorType
protocol.
So what does this all mean?
In practical terms, it means that errors can be emitted in number of different ways with RxSwift:
create { observer in
observer.onError(NSError.init(domain: "NetworkServer", code: 1, userInfo: nil))
}
The above creates a signal (or, in RxSwift terminology, an observable sequence) and immediately emits an error.
Here’s an alternative:
create { observer in
observer.onError(MyDomainSpecificError.NetworkServer)
}
Since an Observable
only enforces that the error must be a type that complies with ErrorType
protocol, you can pretty much send anything you want. But it can get a bit awkward, as in the following case:
enum MyDomanSpecificError: ErrorType {
case NetworkServer
case Parser
case Persistence
}
func handleError(error: MyDomanSpecificError) {
// Show alert with the error
}
observable.subscribeError {[weak self] error in
self?.handleError(error)
}
This won’t work, because the function handleError
is expecting a MyDomainSpecificError
instead of an ErrorType
. You are forced to do two things:
- Try to cast the
error
into aMyDomanSpecificError
. - Handle the case where the
error
is not cast-able to aMyDomanSpecificError
.
The first point is easily fixed with an as?
, but the second is harder to address. A potential solution is to introduce an Unknown
case:
enum MyDomanSpecificError: ErrorType {
case NetworkServer
case Parser
case Persistence
case Unknown
}
observable.subscribeError {[weak self] error in
self?.handleError(error as? MyDomanSpecificError ?? .Unknown)
}
In ReactiveCocoa, since you “fix” the type when you create a Signal<T, E>
or a SignalProducer<T, E>
, the compiler will complain if you try to send something else. Bottom line: in ReactiveCocoa, the compiler won’t allow you to send a different error than the one you are expecting.
Another point for ReactiveCocoa!
UI Bindings
The standard iOS APIs, such as UIKit, do not speak in an FRP language. In order to use either RxSwift or ReactiveCocoa you have to bridge these APIs, for example converting taps (which are encoded using target-action) into signals or observables.
As you can imagine, this is a lot of effort, so both ReactiveCocoa and RxSwift provide a number of bridges and bindings out of the box.
ReactiveCocoa brings a lot of baggage from its Objective-C days. You can find a a lot of work already done, which has been bridged to work with Swift. These include UI Binds, and other operators that have not been translated to Swift. This is, of course, slightly weird; you are dealing with types that are not part of the Swift API (like RACSignal
), which forces the user to convert Objective-C types to Swift ones (e.g. with the use of toSignalProducer()
method).
Not only that, but I feel I’ve spent more time looking at the source code than the docs, which have been slowly falling behind the times. It’s important to notice, though, that the documentation from a theoretical/mindset point of view is outstanding, but not so much from a usage point of view.
To compensate for this, you can find dozens of ReactiveCocoa tutorials.
On the other hand, RxSwift bindings are a joy to use! Not only do you have a vast catalogue, but there are also a ton of examples, along with more complete documentation. For some people, this is enough reason to pick RxSwift over ReactiveCocoa.
+1 point for RxSwift!