5

I have success notification and a failure notification that come through NSNotificationCenter. I wrote some tests to figure out how to combine the signals from those two notifications into one signal that provides an error when the failure notification hits and a next followed by a complete when the success notification hits.

Currently the complete blocks won't get hit, next and error get hit.

Also, secondary bonus question: why doesn't @[errorNotification, completeNotification].rac_sequence.signal do the same thing as the signal of signal creation below?

Code:

-(void)test_flatten_signal_of_signals_and_convert_notification_to_error{
    RACSignal *errorNotification = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"TEST_FAILURE" object:nil] take:1];


    errorNotification = [errorNotification flattenMap:^(NSNotification *notification){
        return [RACSignal error:[NSError errorWithDomain:@"RAC_TEST" code:1 userInfo:nil]];
    }];

    RACSubject *completeNotification = [RACSubject subject];

    RACSignal *signalOfSignals = [[RACSignal
                                   createSignal:^RACDisposable *(id<RACSubscriber> subscriber){
                                       [subscriber sendNext:errorNotification];
                                       [subscriber sendNext:completeNotification];
                                       [subscriber sendCompleted];
                                       return nil;
                                   }]
                                  flatten];


    __block BOOL hitCompleted = NO;

    [signalOfSignals
     subscribeNext:^(id val){
         STFail(nil);
     }
     error:^(NSError *err){
         hitCompleted = YES;
     }
     completed:^{
         STFail(nil);
     }];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"TEST" object:self];

    STAssertTrue(hitCompleted, nil);
}

-(void)test_flatten_signal_of_signals_and_hits_next_complete_on_notification{
    RACSubject *errorNotification = [RACSubject subject];

    RACSignal *completeNotification = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"TEST_SUCESS" object:nil] take:1];

    RACSignal *signalOfSignals = [[RACSignal
                                   createSignal:^RACDisposable *(id<RACSubscriber> subscriber){
                                       [subscriber sendNext:errorNotification];
                                       [subscriber sendNext:completeNotification];
                                       [subscriber sendCompleted];
                                       return nil;
                                   }]
                                  flatten];


    __block BOOL hitCompleted = NO;
    __block BOOL hitNext = NO;
    [signalOfSignals
     subscribeNext:^(id val){
         hitNext = YES;
     }
     error:^(NSError *err){
         STFail(nil);
     }
     completed:^{
         hitCompleted = YES;
     }];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"TEST_SUCCESS" object:self];

    STAssertTrue(hitCompleted, nil);
    STAssertTrue(hitNext, nil);
}
Jon
  • 462
  • 4
  • 13

2 Answers2

11

You can do this with built-in operators:

RACSignal *successNotification = [[NSNotificationCenter.defaultCenter
    rac_addObserverForName:SuccessNotification object:nil]
    take:1];

RACSignal *errorNotification = [[NSNotificationCenter.defaultCenter
    rac_addObserverForName:FailureNotification object:nil]
    flattenMap:^(NSNotification *notification) {
        // Convert to a meaningful error somehow.
        NSError *error = …;

        return [RACSignal error:error];
    }];

RACSignal *signal = [RACSignal merge:@[ successNotification, errorNotification ]];

This takes care of disposal for you, and more obviously indicates how each of the notifications are mapped to a value or an error.

why doesn't @[errorNotification, completeNotification].rac_sequence.signal do the same thing as the signal of signal creation below?

The signal created will send its values asynchronously, unlike the signal you created in your example.

Justin Spahr-Summers
  • 16,893
  • 2
  • 61
  • 79
3

Something like this works for me in both success and failure cases:

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  RACDisposable *success = [[[[NSNotificationCenter defaultCenter]
                            rac_addObserverForName:@"TEST_SUCESS" object:nil]
                            take:1]
                            subscribeNext:^(id x) {
                              [subscriber sendNext:x];
                            } completed:^{
                              [subscriber sendCompleted];
                            }];
  RACDisposable *failure = [[[[NSNotificationCenter defaultCenter]
                            rac_addObserverForName:@"TEST_FAILURE" object:nil]
                            take:1]
                            subscribeNext:^(id x) {
                            [subscriber sendError:
                              [NSError errorWithDomain:@"RAC_TEST" code:1 userInfo:nil]];
                            }];
  return [RACDisposable disposableWithBlock:^{
    [success dispose];
    [failure dispose];
  }];
}];

I create an unique signal with both disposables. The success signals sends “next” and “completed” (the take:1 is important for the completed to work). The failure signal sends the “error”. The idea is using the subscriber sent into the block to forward the events in the other two signals properly.

yonosoytu
  • 3,319
  • 1
  • 17
  • 23
  • Thanks for the reply... The only change is that I flatten mapped the failure notification signal into [RACSigna error:] instead of subscribing next I just subscribe error and pass it on. – Jon Aug 04 '13 at 15:51
  • Look at Justin answer to this question. His answer didn’t need an explicit subscriber, which is always better. – yonosoytu Aug 08 '13 at 19:18