NSArrays and ReactiveCocoa

Johan Attali bio photo By Johan Attali

If you never heard of ReactiveCocoa, I suggest you start by reading the Functional Reactive Programming Guide on iOS made by @AshFurrow. It's comprehensive guide on everything you need to know to get started (Functional Programming, Signals, Streams, Sequences ...).

ReactiveCocoa is a great tool to deal with everything that's happening to your view or your model. It's heavily based on the KVO model and so it's very easy to know when a property of your model changes.

// watch for propertyToWatch and trigger a block when it changes
[[RACObserve(self.modelObject, propertyToWatch) distinctUntilChanged] subscribeNext:^(NSNumber* newValue) {
    if (newValue.boolValue) {
        // do something useful

But there's one thing that ReactiveCocoa will not be very helpful it's when the property you want to watch is a subclass of NSArray. This is mainly due to the fact that the subscribeNext: method takes one input as a parameter in its block that's the new value of the property.

When dealing with NSArray objects you often want to know whether the array got bigger or smaller and knowing only the new value won't be of any help in that matter.

There is a workaround but it's a bit hidden in the documentation. The solution is to use the NSKeyValueObservingOptionNew & NSKeyValueObservingOptionOld parameters when creating the RACSignal.

// this will give us old and new value for the mutableArray property when it changes
RACSignal* signal = [self rac_valuesAndChangesForKeyPath:@keypath(self,mutableArray)

[signal subscribeNext:^(RACTuple* tuple) {
    // the changes dictionary is stored in the second property of the RAC Tuple
    NSDictionary* changes = tuple.second;

    NSArray* oldArray = changes[NSKeyValueChangeOldKey];
    NSArray* newArray = changes[NSKeyValueChangeNewKey];
    NSIndexPath* indexpath = nil;

    [self.tableview beginUpdates];

    // this all comes down to this comparison, here the update is animated but need not to be.
    if (newArray.count > oldArray.count) {
        indexpath = [NSIndexPath indexPathForRow:newArray.count-1 inSection:0];
        [self.tableview insertRowsAtIndexPaths:@[indexpath] withRowAnimation:UITableViewRowAnimationAutomatic];
    else if (newArray.count < oldArray.count) {
        indexpath = [NSIndexPath indexPathForRow:oldArray.count-1 inSection:0];
        [self.tableview deleteRowsAtIndexPaths:@[indexpath] withRowAnimation:UITableViewRowAnimationAutomatic];

    [self.tableview endUpdates];