Search code examples
iosobjective-creactive-cocoa

ELI5: (Reactive cocoa ) - React to a Filter returning no


I am very new to reactive cocoa and I'm still getting my head around it.

But I feel like this should be possible:

  1. Create a signal around a search for a file. (imageSearchSignal)
  2. If the the image isn't found use the download signal and download it. (downloadImageSignal)

So what I'm doing is the following, but what I want to do is add the downloadImageSignal if the image search fails. (basically respond to the filter saying no)

    [[[imageSearch filter:^BOOL(NSString *path) {

        if (path.length > 0) {
            return YES;
        }
        return NO;
    }] map:^id(NSString *path) {

        return [UIImage imageWithContentsOfFile:path];
    }] subscribeNext:^(UIImage *image) {

        // because I want to filter it this would never really happen.  I send no if length is 0.  
        if (!image) { // feel like this should be done in the pipeline somewhere. 
            [[self signalForImageDownload:stepId] subscribeNext:^(UIImage *downloadedImage) {
                [subscriber sendNext:downloadedImage];
                [subscriber sendCompleted];
            }];
        }
        else {
            [subscriber sendNext:image];
            [subscriber sendCompleted];
        }

    } error:^(NSError *error) {
        NSLog(@"error %@", error);

    }];

Example of the flow I'm looking for: Example of the flow I'm looking for


Solution

  • I take it that whole block is wrapped in a createSignal:, hence the calls to subscriber. But that's probably not necessary. Instead of:

    RACSignal *imageSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber>subscriber) {
        [[[imageSearch filter:^BOOL(NSString *path) {
        ...
    

    Just assign it to be a derived signal:

    RACSignal *imageSignal = [[[imageSearch filter:^BOOL(NSString *path) {
        return path.length > 0;
    }] map:^id(NSString *path) {
        return [UIImage imageWithContentsOfFile:path];
    }] flattenMap:^(UIImage *image) {
        if (image) {
            return [RACSignal return:image];
        } else {
            return [self signalForImageDownload:stepId];
        }
    }];
    

    flattenMap is like mapping into a signal of signals and then flattening the result. Since in one of the cases you don't need to do any work, you can use return: to create a dummy signal of just one value.

    However, depending on the behavior of imageSearch, this might not be quite what you want. This will give you one image for each imageSearch (or more, if signalForImageDownload sends more than one next!), but there's a way to throw out old values as soon as imageSearch sends another value: change flattenMap to map followed by switchToLatest.