In my experience, there are three simple ways to think about using ReactiveCocoa:
Here’s the current state of using Key-Value Observation to observe and respond to changes to a string property on my fictional class:
// In your viewDidLoad/awakeFromNib/init
[self addObserver:self
forKeyPath:@"someString"
options:NSKeyValueObservingOptionNew
context:&someStringChangeContext];
// In dealloc
[self removeObserver:self
forKeyPath:@"someString"
context:&someStringChangeContext];
// Elsewhere in your class
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == &someStringChangeContext) {
if ([keyPath isEqualToString:@"someString"]) {
// Do a bunch of stuff here
}
}
}
Here’s the same thing using ReactiveCocoa:
RACSignal *stringChanged = RACObserve(self, someString);
[stringChanged subscribeNext:^(NSString *string) {
// Do a bunch of things here, just like you would with KVO
}];
There’s no need to tear down the observer when you’re done, and if you’re only interested in using ReactiveCocoa as a simple replacement for KVO, there you go! “Bam” said the lady.
The real drive behind the GitHub team building ReactiveCocoa is something called “Functional Reactive Programming” (or FRP). I’m the first to admit that I don’t always understand the most elegant way to approach problems in Cocoa apps using FRP, but the basic gist is that you’re removing unnecessary state from your code.
In the KVO example above, we’re dealing with a string property on a fictional class instance, the actual real-world use of this would not be so abstract. It’s likely that we’re taking a string value from some data source and displaying it in a text field. So here’s that:
RACSignal *nameSignal = [RACObserve(self, someManagedObject.name) distinctUntilChanged];
RAC(self, someTextField.text) = [nameSignal deliverOn:[RACScheduler mainThreadScheduler]];
Still with me? Every time the self.someManagedObject.name
attribute changes, that change will automatically propagate to the text field. Conceptually, it’s quite a lot like Cocoa Bindings, but unlike bindings it’s available for use on iOS.
But that’s still not the real advantage of ReactiveCocoa. Consider this, you have an NSDate
property on one of your objects that you’d like to display in a text field. ReactiveCocoa can help you do that, while providing the same binding-style advantages as above:
RACSignal *dateSignal =
[RACObserve(self, managedObject.startDate) distinctUntilChanged];
RAC(self, someTextFieldForShowingTheDate.text) =
[[dateSignal map:^NSString *(NSDate *date) {
return [NSDateFormatter
localizedStringFromDate:date
dateStyle:NSDateFormatterShortStyle
timeStyle:NSDateFormatterShortStyle];
}] deliverOn:[RACScheduler mainThreadScheduler];
Now any time the date changes on the managed object, the text field will automatically update as well, converting from NSDate
to NSString
as it goes. Nifty, hey?
So ReactiveCocoa, yay! More succinct code, yay! “Should I be using this right now?” I hear you ask?
In the course of adopting ReactiveCocoa for use in my own projects, I’ve discovered some not awesome things:
- (void)viewDidLoad
method turns into a RACParty;So ReactiveCocoa is not all peaches and cream, but to date I’ve also noticed no performance issues and I feel like I’m expressing myself more clearly through RAC-enabled code than I was beforehand.
After my experiences, I could happily just stick to using the -subscribeNext: and -map: methods in a very simple, KVO-esque manner.