Note: This is going to be a slightly more technical post geared toward our friends in the iOS developer community.
Objective-C is a programming language which often finds itself mired in the antiquated ways of C, the programming language upon which modern Objective-C is built. While improvements in computing power have advanced the state of art in programming language design, Objective-C sometimes seems stuck in the past.
Objective-C and C are imperative programming languages where software developers provide statements that the computer executes in sequence. Behaviour emerges from executing these instructions. If a developer has written the correct instructions in the correct order, then the behaviour that emerges should satisfy the requirements of the program.
However, the instructions often have flaws. We use manual and automated testing to mitigate the risk of these flaws, but it would be better if we could abstract away the individual instructions and focus solely on the desired behaviour. That’s where declarative programming comes in.
The imperative paradigm forces developers to write how a program will solve some task. The declarative paradigm frees developers to describe what the task is.
ReactiveCocoa is an approach to making Objective-C less imperative and more declarative. It abstracts much of the how and focuses on the what.
Let’s take a look at a few examples to see what a declarative program in Objective-C might look like.
At the core of ReactiveCocoa is a signal. A signal represents a stream of events that occur over time. Subscribing to a signal allows developers to access those events. Let’s take a look at a basic example.
A text field in an iOS app can provide a signal which produces events when its text changes. ReactiveCocoa provides a category on
UITextField with the function
rac_textSignal, which we can subscribe to.
Here, we’ve declared that when the text field’s text changes, its new value is logged. Whenever the text field’s signal emits an event, the block is executed and passed the updated value.
The nice thing about signals is that they can be composed. Let’s filter the signal returned by
rac_textSignal to make sure that the length of the string is at least three characters before we log it.
filter: returns a new signal. When the first signal emits an event, the value of that event is passed to the filter block. If the block returns
YES, then the new signal emits an event. The subscription is to the signal returned by
Let’s do something more complex. Let’s combine two signals from two different text fields and reduce their values into a boolean which we then bind to the enabled property of a button.
enabled property of the button is always derived from the latest signal values from the two text fields. This represents one of the core components of FRP: deriving state.
In each of these cases, we make declarations once in
viewDidLoad and the statements stay true for the duration of the application runtime. There are no delegate methods to implement or state to store. Behaviour is explicitly declared instead of implicitly inferred.
FRP can get much more complex, and learning the nuts-and-bolts of ReactiveCocoa takes time. However, this time is an investment that will yield more stable programs with predictable, well-defined behaviour.
We’ve seen throughout computing history that software development tends toward higher levels of abstraction. We no longer, for instance, deal with punchcards or assemblers. I believe that FRP represents another level of abstraction which programmers should leverage to build better applications, faster.