Search code examples
iosobjective-creactive-cocoa

Understanding RACSignal Error


I'm currently taking my first steps in ReactiveCocoa and I experience some steep learning curve to understand the principals.

Anyway here's what I already came up with.

I bind a NSArray property to a RACSignal to be able to react to the incoming JSON data over network.

- (void)updateRandomUserData 
{
    @weakify(self);
    RAC(self, users) = [[self fetchRandomUserData] doNext:^(NSDictionary *json){
    @strongify(self);
    NSMutableArray *randomUsers = [NSMutableArray array];
        for (NSDictionary *dict in json[@"data"]) {
           BKRandomUser *randomUser = [MTLJSONAdapter modelOfClass:[BKRandomUser class] fromJSONDictionary:dict error:nil];
           [randomUsers addObject:randomUser];
        }

        self.users = randomUsers;
    }];
}

The signal creation looks like this:

- (RACSignal *)fetchRandomUserData
{

    return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

      NSURLRequest *request = [NSURLRequest requestWithURL:url];

      AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
      [subscriber sendNext:JSON];
      } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON, NSError *error) {
        [subscriber sendError:error];
      }];

      [operation start];

      return [RACDisposable disposableWithBlock:^{
          [operation cancel];
      }];

      }] doError:^(NSError *error) {
        NSLog(@"error: %@", [error description]);
    }];

}

Now when the web service doesn't provide any data I want to react to this. Right now the app crashes with the following statement, which I honestly don't understand:

* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Received error from name: [[+createSignal:] -doError:] -doNext: in binding for key path "users" on : (null)'

What am I missing here?

Thank you!


Solution

  • The accepted answer will work around the problem, but there's a more idiomatic, elegant way to handle this:

    First off, instead of using -doNext:, use -map: to transform your JSON into the array of users:

    RAC(self, users) = [[self fetchRandomUserData] map:^(NSDictionary *json){
        NSMutableArray *randomUsers = [NSMutableArray array];
        for (NSDictionary *dict in json[@"data"]) {
           BKRandomUser *randomUser = [MTLJSONAdapter modelOfClass:[BKRandomUser class] fromJSONDictionary:dict error:nil];
           [randomUsers addObject:randomUser];
        }
    
        return randomUsers;
    }];
    

    Then, to handle the error, you can use -catch::

    RAC(self, users) = [[[self fetchRandomUserData] map:^(NSDictionary *json){
        NSMutableArray *randomUsers = [NSMutableArray array];
        for (NSDictionary *dict in json[@"data"]) {
           BKRandomUser *randomUser = [MTLJSONAdapter modelOfClass:[BKRandomUser class] fromJSONDictionary:dict error:nil];
           [randomUsers addObject:randomUser];
        }
    
        return randomUsers;
    }] catch:^(NSError *error) {
        return [RACSignal return:@[]];
    }];
    

    In this example, if an error happens we catch it and replace it with an empty array. You could do whatever you wanted there. Replace it with nil, or +[RACSignal empty] if you just want to ignore the whole thing. Or call another method that returns a RACSignal *.