Adventure to ReactiveCocoa-land

If you work on iOS development, you might have heard about ReactiveCocoa lately. What is that all about? Why are people paying attention to it? Should you be paying attention to it?

Google Trends of "ReactiveCocoa" search term

Image source: Google Trends of "ReactiveCocoa" in Apr 2014

I will walk through a sample app that shows how a simple user interaction can be implemented using both the traditional approach as well as the ReactiveCocoa approach. You can see their differences and decide whether or not ReactiveCocoa is worth your time.

This is the main point I want to bring out:

ReactiveCocoa codes are simply describing signal graphs!

That is all it does, really.

A quick non-introduction about ReactiveCocoa

There are already a lot of great introductions out there so I will just skip all that. Some pointers are probably helpful: This is the official ReactiveCocoa project intro and there are two good posts here and here.

I will just share one fact about ReactiveCocoa that you may find interesting: ReactiveCocoa was initially created and open-sourced by GitHub staff members who created GitHub for Mac.

A simple ReactiveCocoa app

This is what the app looks like. I have shared the source codes in GitHub.

Screenshots of my ReactiveCocoa sample app

There are two tabs, each having the same elements and behaving in the exact same way. Behind the scene, one is implemented with traditional approach while the other one uses ReactiveCocoa.

What does it do?

  • When the name is "valid", i.e. length >= 5 characters,
    • The star symbol will turn blue,
    • Otherwise it will be in light gray.
  • When the "Submit" button is tapped,
    • It will say "Name is too short" if name is invalid,
    • Otherwise it will say "Welcome ".

The old-school, non-ReactiveCocoa way

With traditional programming design pattern, you may do the following like I did in my sample app:

  • Add a callback method to process any changes in text field, e.g. Listen to UIControlEventEditingChanged event using this method,
    • - (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;
    • Perform some logic like, e.g. If length > 4 then change the star icon AND set a flag isNameValid to YES
  • Add a callback method to process clicking of button, e.g. it will check the flag isNameValid and display the corresponding text message.

This is known as imperative programming. The word imperative means "of the nature of or expressing a command; commanding." In this programming style we specify the immediate steps that need to happen in the callback methods. "Imperative" is used in opposition to declarative described below. This traditional style is also often called procedural programming.

There is nothing wrong with this traditional approach. It is how most of us usually code afterall. However...

In this simple example we used only one variable, isNameValid, to track the state of our program. Now, imagine your state consists of a lot more variables that are updated from different methods and different classes, and... there is a bug somewhere that is causing an undesirable behavior. Does this nightmare sound familiar?

But is there any other way, any better way, to do it? Well, you can write better comments, cleaner codes, couple/decouple your classes to make them less error-prone, etc... sure. Let's see what ReactiveCocoa has to offer, shall we?

The ReactiveCocoa way

With ReactiveCocoa, which is functional programming rather than procedural, you would do the following instead.

First, create some signals:

  • Create a signal by using the name field as a signal source.
    • Everytime the name field is edited, a "next" event will be sent from this signal. "Next" is a ReactiveCocoa term for the normal type of events. The other 2 types of events are "complete" and "error". Each "next" event carries a value and in this case it's the text entered in text field.
    • The signal is already available as self.nameField.rac_textSignal because ReactiveCocoa framework has implemented signals for all standard UI components using categories.
  • Create another signal by transforming the name field signal above.
    • This new signal will send a YES value when it receives a name text with >= 5 characters, or NO value otherwise.
    • Let's call this nameIsValidSignal.
  • Create a signal using Submit button as the signal source.
    • It will send a value whenever the Submit button is tapped.
    • Let's call this buttonTappedSignal.
  • Create another signal by combining nameIsValidSignal and buttonTappedSignal.
    • This new signal sends a value whenever it receives message from buttoTappedSignal, and will forward the content of nameIsValidSignal.
    • Let's call this buttonTappedSignalWithInfo.

To re-cap, we just walked through the following:

RACSignal *nameIsValidSignal = [self.nameField.rac_textSignal map:^id(NSString *value) {
    return @(value.length >= 5);
  }];
  
RACSignal *buttonTappedSignal = [self.submitButton rac_signalForControlEvents:UIControlEventTouchUpInside];
  
// Create a signal that is the same as nameIsValidSignal, except that it only sends value when button is tapped.
RACSignal *buttonTappedSignalWithInfo = [nameIsValidSignal sample: buttonTappedSignal];

That was just setting up the signals. The app then needs to describe what should happen when signal values are observed:

  • Create an observer of nameIsValidSignal
    • It will turn the star icon blue or gray depending on the most recent signal value, i.e. content of the nameField.
    • i.e. [nameIsValidSignal subscribeNext: ^(NSNumber *isNameValid) { //...change color here depending on name validity... }];
  • Create an observer of buttonTappedSignalWithInfo.
    • When it receives a signal value, it means either the button is tapped or the name value has changed. It will check these values and display a message accordingly.

You no longer maintain state variables and check them in callback methods. Instead, you treat input sources as signal sources and create observers of the signals, which will appropriately handle any values passed down from the signal sources.

How is this useful? A direct quote from Josh Abernathy, one of the initial and major ReactiveCocoa contributors:

less state, less boilerplate, better code locality, and better expression of our intent.

Functional programming is also known as declarative programming because you "declare" the desired behavior rather than describing each action required for each scenario.

ReactiveCocoa calls itself "reactive" because, when this functional programming paradigm is applied in iOS, programs that changes output according to input can be programmed efficiently. And that's what a lot of apps do.

Understanding and designing ReactiveCocoa codes

ReactiveCocoa codes can seem confusing if you are not familiar with the use of macros and blocks in Objective-C. If you have no clue at all, I highly recommend that you read up on them first as they are used extensively in the ReactiveCocoa framework.

To make it worse, ReactiveCocoa requires you to re-wire your brain and work with a whole new way of programming design pattern described above. It is worth stressing this tip again,

ReactiveCocoa codes are simply describing signal graphs!

What does that mean? The following is the signal graph of the example discussed earlier:

Signal graph of a ReactiveCocoa example

When you read or design codes with ReactiveCocoa, consider how a signal grpah can precisely capture the behavior of your app. You need to ask these questions:

  • What are the signal sources?
    • There are many to choose from: UI components, gestures like tapping/swipping, external events such as network request completions, or simply changes in any variable.
  • Should the signal values be transformed/combined?
    • ReactiveCocoa is powerful because you can perform operations on signal. You can combine multiple signals, change signal values, add/skip signal messages and more.
  • How will they be consumed?
    • When the signal values change, what will happen? Does the UI need to be updated? Send/cancel a network request? Update a variable?

Reference

Adventure to ReactiveCocoa-land
Share this