18

Could someone provide a one-line example of using ReactiveCocoa abstractions to achieve something like this:

// pseudo-code
NSMutableArray *array = @[[] mutableCopy];
RACSignal *newValue = RACAbleWithStart(array); // get whole array or maybe just added/removed element on push/pop

[newValue subscribeNext:^(NSArray *x) {
  // x is whole array
}]

[newValue subscribeNext:^(id x) {
  // x is new value
}]

[newValue subscribeNext:^(id x) {
  // x is removed value
}]

I see that some extensions for NSArray were removed in favor of Mantle https://github.com/ReactiveCocoa/ReactiveCocoa/pull/130 But still can't find simple example of NSArray manipulation.

Justin Spahr-Summers
  • 16,893
  • 2
  • 61
  • 79
Nik
  • 9,063
  • 7
  • 66
  • 81

2 Answers2

15

You can't observe an array for changes. ReactiveCocoa uses key-value observation. Which, as the name suggests, only observes changes to keyed attributes (dictionary members, properties, etc.).

What you can do is observe an array property for changes:

@interface Blah : NSObject
@property (copy, readonly) NSArray *arrayProperty;
@end

// later...
Blah *blah = [Blah new];
[RACObserve(blah, arrayProperty) subscribeNext:^(NSArray *wholeArray){}];

If you want to know which objects where inserted/removed then you have two options. You could work it out by storing each array and comparing each with the previous. This is simplest but will perform badly with very large arrays. AFAIK, ReactiveCocoa does not have built-in operations to do this.

Or you could implement KVO collection accessors and ensure that changes to the array are made using mutableArrayValueForKey:. This avoids creating a new array whenever any changes are made, and also notifies observers of changes made to the proxy array returned by mutableArrayValueForKey:.

Observing change info with ReactiveCocoa is slightly more involved:

RACSignal *changeSignal = [blah rac_valuesAndChangesForKeyPath:@keypath(blah, arrayProperty) options: NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld observer:nil];
[changeSignal subscribeNext:^(RACTuple *x){
    NSArray *wholeArray = x.first;
    NSDictionary *changeDictionary = x.second;
}];

The change dictionary tells you what kind of change was made to the array, which objects were inserted/removed, and the indexes of the inserted/removed objects.

It is documented at: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Protocols/NSKeyValueObserving_Protocol/Reference/Reference.html

Chris Devereux
  • 5,453
  • 1
  • 26
  • 32
  • It's worth saying that to get the old/new value on the `changeDictionary` one can simply use the `NSKeyValueChangeOldKey` and `NSKeyValueChangeNewKey` keys. I've created in a simple [blog post](http://www.dispatchasync.com/ios/reactivecocoa/nsaray-and-reactivecocoa) based on @ChrisDevereux answer – apouche Jul 08 '14 at 07:17
  • @apouche your blog post link is broken. Here is the proper link (for others): http://www.dispatchasync.com/blog/nsaray-and-reactivecocoa/ – Stunner Dec 11 '15 at 20:31
2

The swift equivalent for Chris' solution:

let signal = self.object.rac_valuesAndChangesForKeyPath("property", options:      NSKeyValueObservingOptions.New | NSKeyValueObservingOptions.Old, observer:self.object)
signal.subscribeNext { (object) -> Void in
   if let tuple = object as? RACTuple {
        var wholeArray = tuple.first as? NSArray
        var changeDictionary = tuple.second as? NSDictionary
    }
}

Also make sure you change your contents property in KVO compliant ways.

// This is wrong and wont send values to RAC signals
[self.contents addObject:object];

// This is correct and will send values to RAC signals
NSMutableArray *contents = [account mutableArrayValueForKey:@keypath(self, contents)];
[contents addObject:object];

Edit: To make things more clear, put the name of your array in place of property. For example:

lazy var widgets:NSMutableArray = NSMutableArray()
let signal = self.rac_valuesAndChangesForKeyPath("widgets", options:      NSKeyValueObservingOptions.New | NSKeyValueObservingOptions.Old, observer:self)
Antoine
  • 23,526
  • 11
  • 88
  • 94
  • Sorry for being ignorant, but what do I put for "property" in swift? I have an array of integers and when I update that array, I want to be up dated via RAC to then update the UI. Here is a gist of what I have so far but it's crashing when I try and create the signal. https://gist.github.com/twilly86/61e99b4f2dd8ecf90fb7 – TWilly Nov 24 '14 at 18:55
  • In your example you should use "numberArray". Also, to make things more clear, I defined my property as 'lazy var widgets:NSMutableArray = NSMutableArray()' where I'm using "widgets" instead of "property". I've updated the question above. – Antoine Nov 24 '14 at 19:00