4

I have an NSArray of ViewModel objects on my ViewController:

@property (nonatomic, strong) NSArray *viewModels;

A ViewModel object looks something like this:

@interface ViewModel : NSObject

@property (nonatomic) BOOL isSelected;

@end

I am trying to create a RACSignal for the enabledSignal on a RACCommand's init method:

- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock

This signal will tell the Command to be enabled if there are either 0 viewModel objects selected or if the number of viewModels selected is equal to the total count of the viewModels.

I can create a RACSequence which will give me the viewModel objects that are selected by this code:

RACSequence *selectedViewModels = [[self.viewModels.rac_sequence

                                        filter:^BOOL(ViewModel *viewModel) {
                                            return viewModel.isSelected == YES;
                                        }]

                                       map:^id(ViewModel *viewModel) {
                                           return viewModel;
                                       }];

How would I go about creating the valid signal?

strickland
  • 1,943
  • 5
  • 21
  • 40

1 Answers1

7

To observe all of the latest view models (and only the latest view models) for changes, we'll need to set up new KVO observations each time the array changes.

The most natural way to represent this is with a signal of signals. Each "inner" signal represents a set of observations on one version of viewModels, and then we'll use -switchToLatest to ensure that only the newest signal takes effect:

@weakify(self);

RACSignal *enabled = [[RACObserve(self, viewModels)
    // Map _each_ array of view models to a signal determining whether the command
    // should be enabled.
    map:^(NSArray *viewModels) {
        RACSequence *selectionSignals = [[viewModels.rac_sequence
            map:^(ViewModel *viewModel) {
                // RACObserve() implicitly retains `self`, so we need to avoid
                // a retain cycle.
                @strongify(self);

                // Observe each view model's `isSelected` property for changes.
                return RACObserve(viewModel, isSelected);
            }]
            // Ensure we always have one YES for the -and below.
            startWith:[RACSignal return:@YES]];

        // Sends YES whenever all of the view models are selected, NO otherwise.
        return [[RACSignal
            combineLatest:selectionSignals]
            and];
    }]
    // Then, ensure that we only subscribe to the _latest_ signal returned from
    // the block above (i.e., the observations from the latest `viewModels`).
    switchToLatest];
Justin Spahr-Summers
  • 16,893
  • 2
  • 61
  • 79
  • This fires when the viewmodels are first set but not when an individual viewmodel's isSelected property changes. – strickland Oct 31 '13 at 18:38
  • Kind of like my old C# / MVVM days, if I set the self.viewModels = self.viewModels after an object in the NSArray changes then this will fire. Surely there is a better way? – strickland Oct 31 '13 at 19:10
  • @strickland Ah, sorry, that wasn't clear from the question. I've updated my answer. – Justin Spahr-Summers Oct 31 '13 at 20:31