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 whatthe 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.
The 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 byfilter:
.
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 thenbind to the enabled property of a button.
The 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.