2

I have a method on ParentViewModel which returns an RACSequence of ViewModel objects like so:

- (RACSequence *) viewModels
{
    return [self.models.rac_sequence map:^id(Model *model) {
        return [[ViewModel alloc] initWithModel: model];
    }];
}

Each of the ViewModels has a state property on which is an enum and has 3 states: NotStarted, InProgress and Completed. When all the ViewModels in my sequence have the state Completed I know ParentViewModel is valid. I have a validSignal on the ParentViewModel which I want to derive the fact that is valid from the viewModels sequence. At the moment I have this code:

BOOL valid = [[self viewModels] all:^BOOL(ViewModel *vm) {
        return vm.state == Completed;
    }];

Which gives me an indicator if all ViewModels in the sequence are valid. How can I then turn this into a RACSignal which will update every time the state property on one of the ViewModels changes?

JFoulkes
  • 2,429
  • 1
  • 21
  • 24

1 Answers1

5

You need first to turn state into a RACSignal, and then everything is easy from that point.

The final code will be something like the following:

RACSignal *valid = [[RACSignal combineLatest:
                     [[self viewModels] map:^id(ViewModel *viewModel) {
                       return RACAbleWithStart(viewModel, state);
                     }]
                    ]
                    map:^(RACTuple *states) {
                      return @([states.rac_sequence all:^BOOL(NSNumber *state) {
                        return state.unsignedIntegerValue == Completed;
                      }]);
                    }
                   ];

The first block maps each of your view models into a signal that observes the state property (with the starting value as first value of the signal).

combineLatest: will take a collection of RACSignals and will create a new signal that fires everytime one of the underlaying signals changes, and sends a RACTuple with the value of each signal.

That RACTuple is then converted into a RACSequence, and we can generate a value of @YES or @NO depending if all the values are Completed or not.

I think the result is the signal you were looking for.

(Disclaimer: I’m new to ReactiveCocoa, so there may be an easier way).

yonosoytu
  • 3,319
  • 1
  • 17
  • 23
  • This is a good starting point but I think `combineLatest:` takes fast enumerable objects. The `map:` in this code is simply going to create a single RACSignal which is not an `id` object. This code is not going to compile or going to crash at runtime. You should use `toArray` or `collect` on the `map`ped signal to transform it to an NSArray for `combineLatest:` – allprog Jun 15 '13 at 20:54
  • The code does compile and doesn’t crash, I can assure you, because I have compile and run it myself while answering the question. The `viewModels` (which I have written wrong in my answer at first, but since corrected) are a `RACSequence`, the first `map:` creates another `RACSequence` (of `RACSignals`) which is an `id` as `combineLatest:` wants. The second `map:` creates another `RACSignal` from the previous, which instead of a `RACTuple` of n `states`, it reduces all of them to a single `NSNumber`, which is the signal the user will subscribe and use. – yonosoytu Jun 15 '13 at 22:24
  • Excellent answer. This is a great way to accomplish it. – Justin Spahr-Summers Jun 15 '13 at 22:33
  • Thanks, now it looks fine. It seemed like a single mapped signal was given to combineLatest which is not Ok. I'd suggest you reword the paragraph starting with `combineLatest:` to make this more apparent. Great answer! – allprog Jun 15 '13 at 22:35
  • Amazing. I'm also new to ReactiveCocoa, it's a steep learning curve but thankfully my questions on here seem to get answered in a day so I'm not losing too much time on the project. – JFoulkes Jun 16 '13 at 09:10