Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ


Choose your language

InfoQ Homepage News RxSwift Brings Native Reactive Functional Programming to Swift

RxSwift Brings Native Reactive Functional Programming to Swift

RxSwift project aims to port Rx programming model to Swift, including as many of its abstractions as possible. InfoQ has spoken with Krunoslav Zaher, maintainer of the project.

RxSwift is based on the Observable<Element> interface which is meant to enable easy composition of asynchronous operations and event streams. Observers are in RxSwift equivalent to sequences, so it is possible to model high level operations on elements of a sequence, such as a data or event stream, through operators in the Observable interface.

RxSwift's flexible programming model makes it possible a number of different use cases: bindings, including UI; retries; delegates; KVO; notifications.

The following example shows how you can bind two text fields through the rx_text operator. The value of one the field is associated to the other’s through an async API (WolframAlphaIsPrime). Whenever the value of the former changes, the value of the latter is updated by enqueueing an async call:

let subscription : Disposable  = primeTextField.rx_text      // type is Observable<String>
            .map { WolframAlphaIsPrime($0.toInt() ?? 0) }       // type is Observable<Observable<Prime>>
            .concat()                                           // type is Observable<Prime>
            .map { "number \($0.n) is prime? \($0.isPrime)" }   // type is Observable<String>
            .bindTo(resultLabel.rx_text)                        // return Disposable that can be used to unbind everything

// This will set resultLabel.text to "number 43 is prime? true" after
// server call completes.
primeTextField.text = "43"

// ...

// to unbind everything, just call

InfoQ has spoken with Krunoslav Zaher, maintainer of the project.

Could you explain what change of perspective Rx programming requires?

It all depends on what is your programming background. For people that have most experience working with ObjectiveC or Swift (yes, also Swift) I would say that it requires not to think of it as programming, but as declaring the behavior of a dynamic system.

When one is using observable sequences and transforming them using operators, he/she is actually defining how the resulting observable sequence relates to the source sequences on which the operators were applied. And that might seem rather weird at the beginning. The really important thing is to think of them as observable sequences.

Thinking of observable sequences in different, less accurate, ways could create a lot of problems (streams, signals, pipes, etc). Those other terms are sometimes used to help beginners reason about Rx, but if used for too long, they can just bring a lot of confusion later on. They are also sometimes used because Rx can be used to model stateful parts of the system, but that is only part of the story, and it’s my advice to not think in those terms.

The problem with thinking in those terms (streams, signals, pipes, etc.) is that they carry an implicit assumption:

that Observable when defined carries shared values, and that there is such a thing as current value that you can set from the outside. that Observable can be somehow cancelled directly and behaves like a future/promise.

When in fact:

Observable is just a definition of how to compute the sequence. No computation is performed automatically after the sequences are defined (let result = It is equivalent to SequenceSequence type in Swift. Computation is being performed when an observer subscribes (the equivalent would be calling generate method on SequenceType), and that particular computation can be cancelled by disposing the result Disposable object. Observable can of course represent stateful computations that share underlying subscription to source observable sequences (using shareReplay(1) operator, etc.), but that is not the default behavior.

I think that part of the problem is that maybe people have been using futures/promises, or some simpler reactive libraries that behave in a similar way, so they naturally assume that Rx behaves in a same way when that is not the case, and that can be confusing for beginners for sure.

The weird thing is that those properties on one side cause big problems for people when they are starting with Rx, and sometimes that initial barrier is just too steep and people get discouraged, but on the other hand it’s the reason why for me personally Rx is so beautiful.

The beautiful part is that you can teach somebody Rx in one sentence: Observable<T> represents observable sequence of elements T (equivalent to SequenceType in Swift) on which one can call subscribe (equivalent to generate method on SequenceType) method to register it’s own observer (callback) that will receive elements of that sequence.

Everything else are just details and pretty obvious and natural ones if this part is clear.

It could be problematic to think of Rx as just another library and not a different concept/abstraction, and start looking on Stack Overflow or similar sites for examples, etc., when the only thing one needs to learn about Rx is to just understand that sentence about observable sequences and everything else just makes sense immediately. The steep learning curve disappears.

What are the advantages of adopting Rx?

There are plenty of advantages, and I’ll probably skip a lot of them:

  • you can stop programming, and start declaring how your program needs to behave and let the computer figure out how to deal with transient state and unanticipated situations.
  • you can stop reimplementing the same patterns over and over again and abstract them as operators on observable sequences. Probably the most common ones are:
    • retry
    • combineLatest (combine latest values of multiple stateful objects, similar to what Excel or some other spreadsheets do when calculating formulas)
    • map (transform sequence of values into another sequence)
    • merge (combine events from multiple sources into a single source)
    • flatMapLatest (automatically cancel previous async operation when next computation request arrives)
    • refCount (in case you want to download something and want to make sure that if at least somebody needs that download result, then download should continue, but if nobody needs the download result then download needs to be cancelled automatically)
    • zip (want to make N network requests, wait until they are all completed and map the result?, yep, out of the box)
  • you can unify all different notification and observing mechanisms into one, e.g:
    • delegates
    • notification center notifications
    • KVO
    • Target/Action pattern.
  • by using Rx you do not only chain computation results, but also computation cancellations (disposals) for free, e.g:
    • there are 3 places in the app that request image download, if 2 of them cancel the request, there is still somebody who needs that information, so image download will be continued
    • you need to do some complex data transformations chained by async operations that can possibly fail and once the end operation is disposed, everything is disposed of automatically.

By the way, the are any costs to pay for this?

I don’t think there is any significant performance impact, we are really really performance oriented, and I would say that performance impact is probably less than what people think, and more than what we are pushing it to be.

We have a small performance benchmark contained inside the project that we are using to benchmark operators in terms of processor usage and number of allocations they are performing during usage, so it’s quite easy to test how each operator or composition perform.

The biggest cost IMHO is when people misuse the concept/library. In that case all bets are off. That’s not a problem with the library/concept itself, but still something that I would consider as a potential cost. Not understanding how to use the concept, or using it in a wrong way can be most costly indeed.

If the application isn’t architected as unidirectional data flows, introducing Rx by itself without adapting the architecture probably won’t solve any of the problems automatically. It could even make the problem worse since now you have this another layer that you don’t fully understand how to use. On the other hand there is a lot of cases where it’s pretty obvious how to use RxSwift. E.g. networking layer, observing of data models, etc. So it’s not that hard to start using Rx in a beneficial way, see how it performs, and then slowly build on top of that. We’ve tried to remedy that problem by providing some short examples of how to use it (RxExample project), but it looks like we’ll need to do more work there.

One of the practical problems people can face when using this library is relying too much on call stack debugging technique. Debugging any reactive library in that way is not the right way. We’ve tried to remedy that issue by providing a series of operators that can be used for debugging purposes and trace what is happening in the system.

There is still a lot of work to be done regarding reactive architectures and applying Rx to some concrete problems.

How would you describe RxSwift’s level of maturity Do you know of any projects using it?

I’m personally always skeptical about using some new shiny/silver bullet project, so I’m assuming people will be skeptical about using RxSwift at first, and that’s totally expected.

But on the other hand, we are doing everything on GitHub and in the open.

From the open source apps, probably the most famous that I know of is Artsy’s Eidolon app, and the ecosystem is slowly booting up as well as RxSwiftCommunity on GitHub.

CocoaPods does offer download statistics and number of apps using a certain pod. RxSwift ~> 2.0 started to get a really decent traction, so I’m really excited about the future. We also support Carthage and Swift Package Manager on Linux, but I’m not sure what is the best way to provide more accurate usage statistics.

What’s on RxSwift roadmap?

For 2.0.0 version we don’t expect anything radical to change. We’ll maybe add a couple of small features, improve Linux support, etc.

When we get our hands on Swift 3.0 compiler, then we’ll be able to give more accurate roadmap. The general direction we are moving is: * more type safety * better performance * less features.

Yes, that is correct, we are actually aiming for less features, and pulling everything we can into ecosystem projects. I think the RxSwift library right now is about the size we want to have, and it will be quite a challenge keeping it this size. There is a lot of pressure to add new operators and features.

That means that it will be definitely hard to convince people that it’s a good thing not to include tons of new operators and extensions, and I’m sure people will have their frustrations with this approach, but that’s something we need to do for this project to be maintainable.

I would say that keeping features out of RxSwift will be the hardest part to execute.

What was you experience implementing Rx in Swift? How did the language perform?

Swift is a really nice language, and it’s definitely one of the most beautiful languages I’ve worked with. On the other hand it doesn’t yet have some obvious features that would make it extremely more useful, but it’s still a young language so I’m sure this will be worked out.

If you squint really hard, Swift can look like a clean modern version of C++, but if you take a look at generated code, it’s quite surprising how much runtime overhead does it generate. Some of it is necessary because of Objective C interoperability, but we are hoping that those issues will also be resolved.

Swift compiler is still a toddler, so to say. I guess everything is behaving as expected.

RxSwift tries to be as consistent as possible with [] and its documentation and can be installed from CocoaPods, Carthage, or directly from GitHub.

Rate this Article