Search code examples
reactive-cocoa

Using reactive cocoa to concisely fetch local data and then update with remote data


I am trying to implement a RACCommand that can be executed to initiate display of the freshest available data to the UI.

Both sendNext: calls would return data from the local database and a remote server respectively.

Is there are more concise way to achieve the same behaviour?

self.testObjects = @[ @"initial" ];

RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    RACSignal *s1 = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
        [subscriber sendNext:@[@"value1", @"value2"]];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
            // Cleanup
        }];
    }];

    RACSignal *s2 = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
        [subscriber sendNext:@[ @"value1-updated", @"value2" ]];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
            // Cleanup
        }];
    }];

    return [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
        [s1 subscribeNext:^(id x) {
            [subscriber sendNext:x];
        }];

        [s1 subscribeCompleted:^{
            [s2 subscribeNext:^(id x) {
                [subscriber sendNext:x];
            }];
        }];

        return [RACDisposable disposableWithBlock:^{
            // Cleanup
        }];
    }];
}];

RACSignal *racSignal = [command.executionSignals switchToLatest];
RAC(self, testObjects) = racSignal;

[[self rac_valuesAndChangesForKeyPath:@"testObjects" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial observer:self] subscribeNext:^(RACTuple *x) {
    RACTupleUnpack(NSArray *values ) = x;
    NSLog(@"values: %@", values);
}];

[command execute:nil];

Outputs:

values: ( initial )

values: ( value1, value2 )

values: ( "value1-updated", value2 )


Solution

  • Assuming you can do all of your cleanup in the disposables for each of s1 and s2 (that is, assuming you don't have any global cleanup that you're not mentioning), you can use the -[RACSignal concat:] method to chain these two signals together:

    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        RACSignal *s1 = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
            [subscriber sendNext:@[@"value1", @"value2"]];
            [subscriber sendCompleted];
            return [RACDisposable disposableWithBlock:^{
                // Cleanup
            }];
        }];
    
        RACSignal *s2 = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
            [subscriber sendNext:@[ @"value1-updated", @"value2" ]];
            [subscriber sendCompleted];
            return [RACDisposable disposableWithBlock:^{
                // Cleanup
            }];
        }];
    
        return [s1 concat:s2];
    }];
    

    The -[RACSignal concat:] method in this case will create a signal which delivers all values on s1, and then when s1 completes will deliver values on s2, until it completes (at which time the returned signal will complete).