Search code examples
reactive-cocoa

ReactiveCocoa finally never be called with already completed replaySubject. Is it expected behavior?


Is this expected behavior?

RACSubject *subject = [RACReplaySubject replaySubjectWithCapacity:1];
[subject sendCompleted];
[subject finally:^{
    NSLog(@"never called");
}];
[subject subscribeCompleted:^{
    NSLog(@"called");
}];

If so, how do I have finally like block? Do I need like this?

void (^block)() = ^ {
    NSLog(@"like finally!");
};
[subject subscribeError:^(NSError *error) {
    block();
} completed:^{
    block();
}];

EDIT: For those who misunderstand what finally do like me, the document for renamed doFinished in RAC 3.0 may help you understand, instead of the 2.X document.


Solution

  • It's actually unrelated to the fact that the signal has already completed. Consider this:

    RACSubject *subject = [RACSubject subject];
    [subject finally:^{
        NSLog(@"never called");
    }];
    [subject subscribeCompleted:^{
        NSLog(@"called");
    }];
    [subject sendCompleted];
    

    Still never called! Why? Because finally: returns a new signal; it doesn't modify the existing signal. The new signal will perform those side effects whenever it sends a completed or error to one of its subscribers. If it doesn't have any subscribers, those side effects will never take place. So now let's subscribe to the signal that finally: returns:

    RACSubject *subject = [RACReplaySubject replaySubjectWithCapacity:1];
    [subject sendCompleted];
    [[subject finally:^{
        NSLog(@"called now!");
    }] subscribeCompleted:^{
        NSLog(@"called");
    }];
    

    That works, but might not be what you actually want. finally: doesn't inject side effects into the signal, it injects side effects into each subscription to the signal. So the following thing:

    RACSubject *subject = [RACReplaySubject replaySubjectWithCapacity:1];
    [subject sendCompleted];
    RACSignal *signalWithFinally = [subject finally:^{
        NSLog(@"finally block");
    }];
    [signalWithFinally subscribeCompleted:^{
        NSLog(@"first subscriber completed");
    }];
    [signalWithFinally subscribeCompleted:^{
        NSLog(@"second subscriber completed");
    }];
    

    Will perform the finally: block twice, which may not be what you want. subscribeError:completed: may be a better choice for your use case.