Search code examples
iosobjective-creactive-cocoa

RACCommand separate handling of successful completion


I have a problem with implementing some logical simply code. In simple terms I want to make something like this:

Complete command() {
    if (error) {
        show error alert
    }
    else {
        show "Happy ending" allert
    }
}

I know how to make first part, but with second I have a problem

My try:

[self.vm.applyCommand.errors subscribeNext:^(id x) {
    //Show alert with error
}];

[[[self.vm.applyCommand.executionSignals
    map:^(RACSignal *signal) {
        return [[signal ignoreValues] materialize];
    }]
    concat]
    subscribeNext:^(RACEvent *event) {
        //Show alert with "Happy end"
    }];

In this case everything works as expected until error appears. In case of error I see two alerts (one with error, another with "Happy end"), but want only one with error.

I know why it happens,

Errors will be automatically caught upon the inner signals ...etc

but I want some solution to make desirable behavior.

UPD: "...see two alerts (one with error, another with "Happy end")"


Solution

  • There are two possible ways to achieve what you want, depending on how the execution signal for applyCommand behaves.

    If it sends only one next value and then immediately completes, you can simply use switchToLatest operator to subscribe for that next value and display the alert with 'Happy end':

      [[self.command.executionSignals switchToLatest] subscribeNext:^(id x) {
        //Show alert with "Happy end"
      }];
    

    Otherwise it's much more complicated, because it's hard to distinguish between successful completion and failure of RACCommand's execution signal. In case of error you get a "completed" RACEvent when calling [[signal ignoreValues] materialize];, and then command's errors signal sends the error as its next value.

    I managed to do it using command's executing signal, which sends @NO after the errors signal sends the error. You can use merge and combinePreviousWithStart:reduce operators to check if the command stopped executing because an error occurred:

    RACSignal *stoppedExecuting = [[self.vm.applyCommand.executing ignore:@YES] skip:1];
    RACSignal *merged = [stoppedExecuting merge:self.vm.applyCommand.errors];
    
    [[[merged combinePreviousWithStart:nil reduce:^id(id previous, id current) {
      return @( [previous isKindOfClass:[NSError class]] || [current isKindOfClass:[NSError class]] );
    }] filter:^BOOL(NSNumber *errorOccurred) {
      return !errorOccurred.boolValue;
    }] subscribeNext:^(id x) {
      NSLog(@"Happy end!");
    }];
    

    It's not a perfect solution as it depends on order of sending values from various RACCommand's signals, which is an implementation detail and can change in the future (it worked for me with RAC 2.5). I suppose that this problem might be solved with RAC 3.0, as it replaces RACCommand with Action, but I haven't tried it yet.