Bubble Foundry

Subscribing to collection changes using ReactiveCocoa

by Peter.

When selecting items from a list in an iOS app, e.g. a table view of address book entries, we’ll often keep track of the selected items using a mutable container, whether an array or a set. It, follows, then that we might want to only enable the Submit button once at least one item has been selected.

Let’s subscribe our collection using RACObserve():

[RACObserve(self, selectedRows) subscribeNext:^(NSSet *currentlySelected) {
  NSLog(@"Currently selected: %@", currentlySelected);

This will fire once when we set the property to an empty set:

self.selectedRows = [NSMutableSet set];

But will it fire when we add a object to it?

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  [self.selectedRows addObject:indexPath];

No. Why?

The answer, I believe, has to do with KVO and pointers: because the property’s pointer to the NSMutableSet doesn’t change, no KVO events are fired, despite the object’s contents changing internally.

The solution is to not mutate the property directly, but instead modify it via a proxy which will fire the KVO events for us (every NSObject has this proxying ability):

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  [[self mutableSetValueForKey:@"selectedRows"] addObject:indexPath];

The one disadvantage of this approach is that you must remember to always use the proxy collection. While I haven’t looked into it, it is my understanding that there are other solutions which can avoid this, though they require you to declare your own separate proxy container class containing the logic.