Note: This is going to be a slightly more technical post geared toward our friends in the iOS developer community.
In my previous post, I covered high-level aspects of ReactiveCocoa, the Objective-C framework that allows developers to write apps declaratively. Now I’d like to introduce some patterns in ReactiveCocoa, discuss a few best practices, and outline common gotchas to be on the lookout for. ReactiveCocoa can have a steep learning curve – let’s climb it together.
There are three basic patterns to using ReactiveCocoa: chaining, splitting, and combining. I covered the first two last time, but let’s go back for an in-depth look.
Recall that the core of ReactiveCocoa is the signal: it represents changing state over time. When we chain, split, or combine, we’re operating on those signals.
Chaining is the most common pattern in ReactiveCocoa – it’s when you have one signal and you transform it into a new one. Common ways to create new signals are by using filter:, map:, and startWith:. Let’s take a look at an example.
In this example, we’re binding the text property of the textField to be the result of a chain of three signals. First, we create an interval signal that sends a new value of the current time every second. When we create this signal, it doesn’t have a value yet (and won’t for another second), so we start that signal with startWith:. Finally, we use map: to transform the NSDate value from the signal into a string, which will be assigned to the text property of textField.
Chaining is the most common of operators and it’s often done inline without storing the signals in local variables. The above example is equivalent to the following.
Splitting is similar to chaining in that it transforms signals into other signals, but different in that it reuses the intermediate signals. Splitting sounds complicated, but it’s as easy as using a signal more than once.
In this example, we’ve created a signal by chaining a few signals together, then stored it in an local variable named dateComponentsSignal. We then use that one signal to create two new signals and bind those individually to two text fields.
The last common pattern is combining. Combining is when you create a new signal from several signals. For example, if we wanted to create a signal to determine the enabled state of a submit button, we could combine the latest values from two text fields’ signals.
Here we’re binding the enabled state of our button to a signal created with combineLatest:reduce:. The second parameter is a block which is itself passed parameters that are the latest values from the signals being combined. Since we are combining two signals that send NSString values, the reduce block takes two NSString parameters. It’s this block’s job to reduce those two down to one value.
Combining is common when you need to wait for several conditions to be met or need to make a choice between the latest values carried by several signals.
It’s important to think of the linear flow of logic and how the values sent by signals are chained, split, or combined. This flow defines the application logic and it can be helpful to draw these diagrams when starting to use ReactiveCocoa. Reading about the Basic Operators can also help you become more familiar with these patterns.
Now that we’ve covered the basics of ReactiveCocoa patterns, let’s dive into some best practices.
ReactiveCocoa is a tool that programmers can use to make writing apps easier by removing state. However, even in a “completely reactive” app, you have to deal with non-ReactiveCocoa code. Things like table view delegate methods, for example. When you want to bridge the gulf between non-reactive and reactive worlds, use RACSubjects.
A RACSubject is a signal which can be manually sent new values. For example, imagine if gesture recognizers weren’t a part of ReactiveCocoa – we might use two RACSubject properties to receive events from a gesture recognizer: one for whether or not the recognizer is underway, and one for its current location.
We’ve bound the frame of a view to be centred under the latest location of the gesture recognizer.
Sending the subjects events is as easy as implementing a very stripped-down gesture recognizer method.
While RACSubjects are great for bridging non-reactive code to ReactiveCocoa code, there is a danger in over-using them. Don’t rely on sending explicit values when chaining signals together would work instead.
ReactiveCocoa is designed to eliminate as much state as possible from our applications. This reduction in state leads to simpler program logic but doesn’t allow us to easily perform side-effects. For example, consider the above gesture recognizer code. We may wish to flash the scroll indicators of a table view whenever the gesture recognizer finishes to let the user know where in the table they’re currently scrolled to. We can do that with an explicit subscription.
Here we’ve filtered out events where the gesture recognizer is running to only subscribe to events when it has completed. Then, in our subscribeNext: block, we perform our side-effect.
While subscriptions are useful and necessary for performing side-effects, be careful not to overuse them. They are like mutable variables – state – which ReactiveCocoa tries to avoid. Don’t use RACSubjects to manipulate application state where binding properties to mapped signals can work instead.
Like any new paradigm, there are gotchas that are likely to trip up beginners. For example, when we create a new signal from a property, like in the following code, nothing will happen until the first value is sent the next time the someString value changes.
If you want to send the current property’s value immediately, use RACAbleWithStart. This “starts” the signal with the current value of the property being bound to.
Similarly, when using interval: to schedule a periodic timer, the timer does not fire until the first interval has passed, much like using NSTimer. Remember the first example from this post where we were binding some text field’s text to be the current time? We started the signal manually with startWith: and sending the current date. If we didn’t then the text field would be empty for a second before the first interval tick.
While we’re on the topic of interval:, it’s important to note that this method delivers its results on the high-priority scheduler (similar to a GCD queue). That means that UI updates cannot be performed directly and the code at the top of this article has a subtle bug. It should be re-written to deliver results of the interval: signal on the main thread scheduler.
This post has covered common patterns, best practices, and some gotchas. Hopefully you’ve gained some insight into how you can leverage ReactiveCocoa to build declarative applications. If you have any questions, the community is quite active on StackOverflow and GitHub.