Search code examples
iosobjective-cuitableview

Load View Controller even if one API fails


I have three API's I pull data from, and put into a UITableView inside of my ViewController.m.

Is there a way to still let the UITableView load if one of the websites isn't loading?

Right now, the ViewController.m just doesn't load if all 3 sources aren't loading per my method.

Here's the method I use:

- (void)loadOneWithSuccess:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success
                   failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure {

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *tNE = [defaults objectForKey:[NSString stringWithFormat:@"tNE%@", bn]];
    NSString *path = [NSString stringWithFormat:@"xx/%@/", tNE];

    [self.eObjectManager getObjectsAtPath:path parameters:nil success:success failure:failure];
}


- (void)loadMedia {
    self.combinedModel = [NSMutableArray array];
    // Here's the #1
    [self loadOneWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {

        [self.combinedModel addObjectsFromArray:mappingResult.array];

    // Here's the trick.  call API2 here.  Doing so will serialize these two requests
    [self loadTwoWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {

        [self.combinedModel addObjectsFromArray:mappingResult.array];

    // Here's the trick.  call API3 here.  Doing so will serialize these two requests
    [self loadThreeWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {

        [self.combinedModel addObjectsFromArray:mappingResult.array];

        [self sortCombinedModel];

        [self.tableView reloadData];

    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        NSLog(@"No?: %@", error);
    }];

    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
            NSLog(@"No?: %@", error);
    }];

    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        NSLog(@"No?: %@", error);
    }];
}

So if API1 doesn't load, API2 and API3 will still load and show in the UITableView in ViewController.m.


Solution

  • The loadOne, loadTwo ... functions have a disadvantage which is that they take two block parameters, one for success and one for fail. If you change those to take a single block that handles success or failure, it will be much easier to carry on after errors occur.

    EDIT Change how you call your eObjectManager by not directly passing on the completion and failure blocks. Instead, implement those blocks and rearrange the params to match the single block interface...

    - (void)betterLoadOneWithCompletion:(void (^)(RKObjectRequestOperation*, RKMappingResult*, NSError *))completion {
    
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        NSString *tNE = [defaults objectForKey:[NSString stringWithFormat:@"tNE%@", bn]];
        NSString *path = [NSString stringWithFormat:@"xx/%@/", tNE];
    
        [self.eObjectManager getObjectsAtPath:path parameters:nil success:^(RKObjectRequestOperation *op, RKMappingResult *map) {
            // success! pass the operation, map result and no error
            completion(op, map, nil);
        } failure:^(RKObjectRequestOperation *op, NSError *error) {
            // fail.  pass the operation, no result and the error
            completion(op, nil, error);
        }];
    }
    

    It can still call your old function or some external library with two blocks, but it combines the result into a single block. The caller of this expects that they will either get a good RKMappingResult and a nil NSError, or a nil for the result parameter and an instance of an error. With this api, we can easily fix your method to just log errors as they occur and carry on, error or not...

    - (void)loadMedia {
        self.combinedModel = [NSMutableArray array];
    
        // changed the loadOneWithCompletion signature to take just a single block, calling it on success or fail
        [self betterLoadOneWithCompletion:^(RKObjectRequestOperation *op, RKMappingResult *mappingResult, NSError *error) {
            // if it worked, handle the results
            if (!error) {
                [self.combinedModel addObjectsFromArray:mappingResult.array];
            } else {
                // if it didn't work, log the error, but execution continues
                NSLog(@"No?: %@", error);
            }
            // even if it didn't work, we can keep going...
            [self betterLoadOneWithCompletion:^(RKObjectRequestOperation *op, RKMappingResult *mappingResult, NSError *error) {
                // same - handle results
                if (!error) {
                    [self.combinedModel addObjectsFromArray:mappingResult.array];
                } else {
                    // same - log the error if there is one
                    NSLog(@"No?: %@", error);
                }
                // same - log the error and keep going
                [self betterLoadOneWithCompletion:^(RKObjectRequestOperation *op, RKMappingResult *mappingResult, NSError *error) {
                    // same...
                    if (!error) {
                        [self.combinedModel addObjectsFromArray:mappingResult.array];
                    } else {
                        NSLog(@"No?: %@", error);
                    }
                    [self sortCombinedModel];
                    [self.tableView reloadData];
                }];
            }];
        }];
    }