ReactiveCocoa From the Ground Floor, Part 1
So you have heard of ReactiveCocoa, and maybe tried to mess around with it a bit, but the learning curve was too high, or maybe you just didn’t see the value in learning it since you already know how to program and you do just fine as it is. Or maybe you have never heard of it and you just happened to stumble upon it here for the first time. This post will cover both groups and attempt to explain in the best way possible the ReactiveCocoa framework, why you should use it, and how it can be used.
So for the uninitiated, ReactiveCocoa is a framework buit by Justin Spahr-Summers and Josh Abernathy with the goal of bringing Function Reactive Programming to Objective-C. What is FRP, you may ask? Well the basic idea of FRP is that you are dealing with time and values, and you can break a program down in such a way as to unify how you deal with all the values that it processes. In other words, you have an system for composing and transforming streams of values. So what does that mean, and how does it look in practice? Think of a button in an iOS app. When the button is tapped, it triggers some method, which then executes some code which may or may not generate a value, and then terminates. Then the button is ready to be touched again, and again execute its code.
When you break down this interaction, you see that the button receives touches over time and generates values for each touch. And even though this example is very simple and contrived, the same concept scales up easily to more complex examples. Imagine getting GPS coordinates from a users phone as they drive. What are you doing in that case? You are receiving a stream of values over time, and then you use those value to generate new values, and then you wait for the next value, and so forth. FRP and ReactiveCocoa provides a unified approach not only to dealing with values received over time, but a way to express the intent of your program in these terms. Most programming languages being used today are imperative languages, which means that that you write a sequence of instructions that get executed line by line. Function languages are declarative, meaning that instead of writing how your program should work, you say what it should do, and let the computer or compiler take care of the rest.
So maybe you understood all that, or maybe you are still confused. Well now we will get into some code examples and you will be able to see more clearly how this all works. So let’s take our button example, and look at it in the normal way, and then in the ReactiveCocoa way. So a normal button setup would go something like this:
1 2 3 4 5 6 7 |
|
Very simple, the button logs something to the console each time it is tapped. Now lets see the same thing in ReactiveCocoa:
1 2 3 4 5 6 |
|
This produces the exact same behavior as the first example but looks quite different. Where was the target added? What method does the button call? This is in viewDidLoad
, won’t it get called immediately? Lets break it down. The first thing I will draw your attention to is the name of the method the button is calling,rac_signalForControlEvents:
, and more specifically, the word signal, as you will be hearing it a lot going forward. So What is a signal? A signal, as defined by the class RACSignal
, represents some arbitrary value that is sent in the form of an object. So what does this signal send? It sends the button.
Next up we have a method called subscribeNext
, that we will break down in two parts. The first part is subscribe, which is also a very important term in ReactiveCocoa. The reason that we could write that code in viewDidLoad
and it did not execute until we pressed the button is that most signals are cold, which means that they don’t do anything until they are subscribed to. So what is a subscriber? A subscriber is anything that registers to receive events From a signal and the event in this case is a button press. So with the call to subscribeNext:
we have created a subscription for receiving events that the signal sends, and more specifically we have subscribed to receive all next events that the signal sends.
There are three types of events a signal can send, and those are next
, error
and completed
. So what we have said is that we want to receive the value of all of the next
events that the signal sends, and that the block passed to subscribeNext:
should be executed each time the signal sends a next
event. The value that the signal sends along with its next
event is the button, so if you wanted to change the block parameter from (id x)
to (UIButton *button)
it would be perfectly valid, as the button is exactly what gets sent. So does that seem pretty simple?
OK, lets diverge a bit and focus on the concept of subscription, as understanding it is very important to understanding ReactiveCocoa. So a signal sends all of it’s events to subscribers, note the plurality of that word, and a signal can easily have multiple subscribers. So what does that mean, and what does it look like in practice? Lets take another example, along with some new code.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
So first, [RACSignal return:@42];
creates a new signal that simply returns the number 42. The doNext:
block will be executed each time the signal it returns receives a new subscriber. So what would you expect to happen when number
is logged? It will print out 4. This is because the doNext:
block is executed each time the signal gets a new subscriber. So it is easy to share signals to multiple subscribers, but care is needed to make sure you don’t repeat work for each subscriber.
Moving on, and keeping with the theme of values over time, lets look at an example that is a bit more useful. imagine that you have a UITextField, and that you need to perform some sort of validation on the text the user enters, and you want to perform this validation in real time, as each character is entered. The normal way of doing this would involve some delegate methods and probably some other method that returns a BOOL
indicating whether or not the field is valid, which in turn updates something else indicating the fields validity. But using ReactiveCocoa, this pattern is incredibly simply:
1 2 3 4 5 6 |
|
In just six lines of code we have a label that will be updated in real time with the validity of the field. UITextField has a property on it called rac_textSignal
which creates a signal that sends the value of the textField each time it is updated. So we take that signal and then apply the map:
function, which takes a value, applies a transformation to that value and then returns a new signal that represents that value. So each time the textField sends a value, we want to check and see if the length of the text is greater than 4, and return a NSNumber wrapped BOOL to indicate this. Then we call map:
on the signal returned from mapping the textField and return a value for either true or false values. Finally, we use the handy RAC
macro which creates a subscription on the textFields text
property, so that it will always be set to whatever next
value is sent from the mapping of textSignal
. So now the user will know immediately when the field is valid or invalid,and you only wrote six lines of code! This is just a small example of what you can do, and I will be following up with a part 2 that will go more into some concrete code examples.