ReactiveCocoa vs RxSwift

A detailed comparison of the two most popular Functional Reactive Programming frameworks for Swift: ReactiveCocoa vs RxSwift! By Colin Eberhardt.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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 ignore this, it will come back and bite you brutally. You have been warned.”

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!

Evil toddler

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:

  1. Next<T>: This event is sent every time a new value (of type T) is pushed into the stream of events. In the locator example, the T would be a CLLocation.
  2. Completed: Indicates that the stream of events has ended. After this event, no Next<T> or Error<E> is sent.
  3. Error: Indicates an error. In the server request example, this event would be sent if you had a server error. The E represents a type that conforms with the ErrorType protocol. After this event, no Next or Completed 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:

  1. Try to cast the error into a MyDomanSpecificError.
  2. Handle the case where the error is not cast-able to a MyDomanSpecificError.

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!

Happy-Crying-Face-Meme-11

Colin Eberhardt

Contributors

Colin Eberhardt

Author

Over 300 content creators. Join our team.