layout | title | ref | category | rating | description |
---|---|---|---|---|---|
post |
ReactiveCocoa |
Open Source |
9.5 |
Breaking from a tradition of covering Apple APIs exclusively, this edition of NSHipster will look at an open source project that exemplifies a brave new era of open source contribution to Objective-C: ReactiveCocoa. |
Languages are living works. They are nudged and challenged and bastardized and mashed-up in a perpetual cycle of undirected and rapid evolution. Technologies evolve, requirements change, corporate stewards and open source community come and go; obscure dialects are vaulted to prominence on the shoulders of exciting new frameworks, and thrust into a surprising new context after a long period of dormancy.
Objective-C has a remarkable history spanning four acts in as many decades:
In its 1st act, Objective-C was adopted as the language of NeXT, powering NeXTSTEP and the world's first web server.
In its 2nd act, Objective-C positioned itself in the heart Apple's technology stack (after a prolonged turf war with Java) with Apple's acquisition of NeXT.
In its 3rd act, Objective-C rose to unprecedented significance with the release of iOS, making it the most important language of mobile computing.
Objective-C's 4th act takes us to the present day, with an influx of new iOS developers from the Ruby, Python, and Javascript communities sparking a revolution in open source participation. For the first time, Objective-C is being directly shaped and guided by the contributions of individuals outside of Apple.
Breaking from a tradition of covering Apple APIs exclusively, this edition of NSHipster will look at an open source project that exemplifies this brave new era for Objective-C: ReactiveCocoa.
For a complete look at ReactiveCocoa, refer to the project's README, Framework Overview and Design Guidelines.
ReactiveCocoa is an open source library that brings Functional Reactive Programming paradigm to Objective-C. It was created by Josh Abernathy & Justin Spahr-Summers in the development of GitHub for Mac. Last week, ReactiveCocoa reached a major milestone with its 1.0 release.
Functional Reactive Programming (FRP) is a way of thinking about software in terms of transforming inputs to produce output continuously over time. Josh Abernathy frames the paradigm thusly:
Programs take input and produce output. The output is the result of doing something with the input. Input, transform, output, done.
The input is all the sources of action for your app. It's taps. It's keyboard events. It's timer triggers, GPS events, and web service responses. These things are all inputs. They all feed into the app, and the app combines them all in some way to produce a result: the output.
The output is often a change in the app's UI. A switch is toggled or a list gets a new item. Or it could be more than that. It could be a new file on the device's disk, or it could be an API request. These things are the outputs of the app.
But unlike the classic input/output design, this input and output happens more than once. It's not just a single input → work → output—the cycle continues while the app is open. The app is always consuming inputs and producing outputs based on them.
To illustrate the difference between the conventional, imperative paradigm of Objective-C programming versus a functional reactive approach, consider the common example of validating a signup form:
- (BOOL)isFormValid {
return [self.usernameField.text length] > 0 &&
[self.emailField.text length] > 0 &&
[self.passwordField.text length] > 0 &&
[self.passwordField.text isEqual:self.passwordVerificationField.text];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
self.createButton.enabled = [self isFormValid];
return YES;
}
In the conventional example, logic is fragmented across different methods in the view controller, with calls to self.createButton.enabled = [self isFormValid];
interspersed throughout delegate methods and view lifecycle callbacks.
Compare this with equivalent code using ReactiveCocoa:
RACSignal *formValid = [RACSignal
combineLatest:@[
self.username.rac_textSignal,
self.emailField.rac_textSignal,
self.passwordField.rac_textSignal,
self.passwordVerificationField.rac_textSignal
]
reduce:^(NSString *username, NSString *email, NSString *password, NSString *passwordVerification) {
return @([username length] > 0 && [email length] > 0 && [password length] > 8 && [password isEqual:passwordVerification]);
}];
RAC(self.createButton.enabled) = formValid;
Here, all of the logic for validating form input is contained in a single chain of logic and responsibility. Each time any of the text fields is updated, their inputs are reduced into a single boolean value, which automatically enables / disables the create button.
ReactiveCocoa is comprised of two major components: signals (RACSignal
) and sequences (RACSequence
).
Both signals and sequences are kinds of streams, sharing many of the same operators. ReactiveCocoa has done well to abstract a wide scope of functionality into a semantically dense, consistent design: signals are a push-driven stream, and sequences are a pull-driven stream.
- Handling Asynchronous Or Event-driven Data Sources: Much of Cocoa programming is focused on reacting to user events or changes in application state.
- Chaining Dependent Operations: Dependencies are most often found in network requests, where a previous request to the server needs to complete before the next one can be constructed.
- Parallelizing Independent Work: Working with independent data sets in parallel and then combining them into a final result is non-trivial in Cocoa, and often involves a lot of synchronization.
Signals send three different types of events to their subscribers:
- The next event provides a new value from the stream. Unlike Cocoa collections, it is completely valid for a signal to include
nil
.- The error event indicates that an error occurred before the signal could finish. The event may include an
NSError
object that indicates what went wrong. Errors must be handled specially – they are not included in the stream's values.- The completed event indicates that the signal finished successfully, and that no more values will be added to the stream. Completion must be handled specially – it is not included in the stream of values.
The lifetime of a signal consists of any number of
next
events, followed by oneerror
orcompleted
event (but not both).
- Simplifying Collection Transformations: Higher-order functions like
map
,filter
,fold/reduce
are sorely missing fromFoundation
.
Sequences are a kind of collection, similar in purpose to
NSArray
. Unlike an array, the values in a sequence are evaluated lazily (i.e., only when they are needed) by default, potentially improving performance if only part of a sequence is used. Just like Cocoa collections, sequences cannot containnil
.
RACSequence
allows any Cocoa collection to be manipulated in a uniform and declarative way.
RACSequence *normalizedLongWords = [[words.rac_sequence
filter:^ BOOL (NSString *word) {
return [word length] >= 10;
}]
map:^(NSString *word) {
return [word lowercaseString];
}];
Capturing and responding to changes has a long tradition in Cocoa, and ReactiveCocoa is a conceptual and functional extension of that. It is instructive to contrast RAC with those Cocoa technologies:
Key-Value Observing is at the heart of all magic in Cocoa—indeed, it is used extensively by ReactiveCocoa to react to property changes. However, KVO is neither pleasant nor easy to use: its API is overwrought with unused parameters and sorely lacking a blocks-based interface.
Bindings are magic—voodoo, really.
Although essential to managing the complexity of a Mac OS X application, Bindings' cultural relevance has waned for years, as the focus has shifted to iOS and UIKit, which notably lacks support. Bindings replace a lot of boilerplate glue code and allow programming to be done in Interface Builder, but they're severely limited and impossible to debug. RAC offers a clear, understandable, and extensible code-based API that works in iOS and is apt to replace all but the most trivial uses of bindings in your OS X application.
Objective-C was built from Smalltalk's ideas on top of C's metal, but its cultural imports go far beyond its original pedigree.
@protocol
was a rejection of C++'s multiple inheritance, favoring an abstract data type pattern comparable to a Java Interface
. Objective-C 2.0 introduced @property / @synthesize
, a contemporary of C#'s get; set;
shorthand for getter and setter methods (as well as dot syntax, which is still a point of contention for NeXTSTEP hard-liners). Blocks injected some functional programming flavor to the language, which paired nicely with Grand Central Dispatch--a queue-based concurrency API almost certainly influenced by Fortran / C / C++ standard OpenMP. Subscripting and object literals, a standard feature in scripting languages like Ruby and Javascript, now finally brought to Objective-C thanks to a Clang language extension.
ReactiveCocoa brings a healthy dose of functional and reactive programming influence to Objective-C, and was itself influenced by C#'s Rx library, Clojure, and Elm.
Good ideas are contagious. ReactiveCocoa is a reminder that good ideas can come from unlikely places, and that a fresh perspective can make all of the difference with familiar problems.