Chris Dzombak

The Value of ReactiveCocoa

This post is largely in response to Soroush Khanlou, via Jason Brennan.

Edited to add, 2014-02-17: Soroush has posted a response, which you shoud also read after this post.

It’s undeniably true that ReactiveCocoa can be difficult to wrap your head around. Really understanding it — really making it click — takes time, thought, and conversation. There’s a learning curve, and certainly the sometimes-terse syntax takes some getting used to.

It’s also true that it’s easy to give trivial examples which make ReactiveCocoa look like a complex solution in search of a problem.

But these statements are true of any tool we have, especially new and unfamiliar ones. And these reasons — lack of understanding and perceived ease of existing solutions — are the absolute wrong reasons to dismiss any programming paradigm outright.

I’d be remiss if I didn’t mention here that RAC 3.0 is already a work in progress. The maintainers are putting a lot of thought into improving the framework, including some changes which will make ReactiveCocoa more approachable.

Posts like this have been written before, but I’d like to try my hand at making a case for ReactiveCocoa. (And I’ll follow with a brief discussion of Soroush’s post.)

Reactive programming

Reactive programming is a different way of thinking about application architecture.

Wrapping your head around it can take some work, especially given that much of the reading available about RAC is very abstract. So for a moment let’s set aside this “composing and transforming streams of values” stuff, and talk about its practical implications.

The key concept in reactive programming is that you specify the flow that data takes through your app. You program the transformations that happen to that data as it flows, and you explicitly specify the side effects that result.

This can seem like a subtle distinction. But it’s an important one: in reactive programming, you describe the data’s flow; in other models, you implement that flow manually.

In traditional imperative Cocoa programming, you write tedious code to maintain and manipulate state in each object. The connections that communicate data between objects — via blocks, delegates, KVO, notifications — are often a second thought at best. It’s easy to miss something and leave the application in an invalid state, and you spend hours trying to ensure that your delegate and block callbacks don’t happen in a wrong order. Proper support for concurrency adds more cognitive load.

The spreadsheet metaphor

Reactive programming tutorials often reference spreadsheets.

It’s an apt metaphor; reactive programming is quite like a spreadsheet. For each cell, you specify where the data comes from and what transformations should be applied to it. As cells’ data is updated, that data flows through the spreadsheet; dependent cells change in response to changes in others.

If you had to program spreadsheets like Cocoa applications, you’d have to subscribe each cell to be notified of changes in cells upstream in the data flow. You’d need to manipulate that data every time the upstream cells changed, update your cell’s UI, and notify all the other cells subscribed to your cell’s data.

Certainly this would be possible, but doesn’t it seem unreasonable?

So why ReactiveCocoa?

Reactive programming allows you to easily and explicitly declare and maintain control over the flow of data through your application, even as it becomes more complex. Though you might not realize it while it’s happening, increasing complexity will force you to slowly outgrow other models.

ReactiveCocoa reduces the time you’d otherwise waste manually managing state across and within objects.

ReactiveCocoa vastly reduces the amount of state you have to store, track, and mutate. Any reduction in mutable state clearly yields fewer opportunities for problems.

ReactiveCocoa also forces you to codify side effects as data fows through your application. This is undoubtedly a good thing: thinking explicitly like this forces you to consider all effects your code has, and helps avoid unexpected consequences.

Soroush’s example

It’s easy, but wrong, to conflate “easy” and “simple”. It’s similarly tempting, and still wrong, to conflate “easy” and “elegant”. With this, and our new knowledge about reactive programming, in mind, let’s reconsider the example Soroush presented in his post.

The ReactiveCocoa code is a trivial example — too trivial to be used as a basis for dismissing RAC. The equivalent would be dismissing Objective-C based on the complexity needed to print “hello, world”. And the ReactiveCocoa example could (should) be rewritten, in a less spaghetti-like structure, to provide better separation of concerns and yield improved readability.

The delegate-based example is easy to understand, but we can’t conflate that with true simplicity or elegance. Indeed, there’s already some hidden complexity in that code snippet, and more will arise as requirements change and that class grows.

Instead, RAC makes explicit the data flow through that application and its side effects, while providing all the other advantages of reactive programming I outlined above.

ReactiveCocoa in the real world

I recently wrote a simple CLI tool to synchronize Github issues into my OS X task management software (Things.app). I used ReactiveCocoa to drive data through the application. You can see the result on Github.

Sure, the ReactiveCocoa syntax will take you some extra mental effort if this is one of your first encounters with RAC code.

But it’s clear how, rather than stringing several asynchronous APIs together with spaghetti delegate- or block-based code, ReactiveCocoa lets me route data throught the app’s components, while requiring me to maintain no state on this object.

Believe me when I say: this was a huge win. My previous, non-RAC approaches were quickly turning into quite an endeavour, even during early stages of development.

Conclusions

Further reading