John Baum John Baum - 4 months ago 12
Objective-C Question

Loading data asynchronously but populating a view in order

I have a use case where I am loading some data using several datasources asynchronously. So, each datasource has a complete method and my view controller implements a protocol with that method defined. For ex, one of my datasources is fetchApples, which returns an array of Fruit objects to my controller and another datasource is fetchOranges etc. In my viewController, I want to have apples show up first, then oranges, then grapes (I populate a uiview with custom cells for rendering the fruits). If there are no apples, oranges should show up first etc. How can I map this order when my datasources are being returned asynchronously. ie. when oranges return, I dont know if i will have apples and thus I cannot populate the uiview with them yet?

Answer

Use a dispatch_group. Each server call will populate its own array of items and you can either add it to one master array (datasource) OR use them separately..

When all the server calls are complete, you'll get notified and you can then reload your table using whichever datasource you want in whatever order you want..

Example (Using dispatch_group and UITableView):

//
//  ViewController.m
//  StackOverflowExample
//
//  Created by Brandon Anthony on 2016-07-16.
//  Copyright © 2016 XIO. All rights reserved.
//

#import "ViewController.h"


typedef NS_ENUM(NSInteger, Fruit) {
    Apples,
    Oranges,
    Grapes
};

#define kImageCellIdentifier @"kImageCellIdentifier"

@interface ViewController () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UIButton *testButton;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSMutableArray *apples;
@property (nonatomic, strong) NSMutableArray *oranges;
@property (nonatomic, strong) NSMutableArray *grapes;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    _apples = [[NSMutableArray alloc] init];
    _oranges = [[NSMutableArray alloc] init];
    _grapes = [[NSMutableArray alloc] init];

    [self initControls];
    [self setTheme];
    [self registerClasses];
    [self doLayout];

    [self loadData];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)initControls {
    _testButton = [[UIButton alloc] init];
    _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
}

- (void)setTheme {
    [_testButton setTitle:@"Test Again" forState:UIControlStateNormal];
    [_testButton setBackgroundColor:[UIColor lightGrayColor]];
    [_testButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [[_testButton layer] setCornerRadius:5.0f];
    [_testButton addTarget:self action:@selector(loadData) forControlEvents:UIControlEventTouchUpInside];

    [_tableView setDelegate:self];
    [_tableView setDataSource:self];
}

- (void)registerClasses {
    [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kImageCellIdentifier];
}

- (void)doLayout {
    [self.view addSubview:_testButton];
    [self.view addSubview:_tableView];

    NSDictionary *views = @{@"testButton":_testButton, @"tableView":_tableView};
    NSMutableArray *constraints = [[NSMutableArray alloc] init];

    [constraints addObject:[NSString stringWithFormat:@"H:[testButton(%d)]-%d-|", 150, 15]];
    [constraints addObject:[NSString stringWithFormat:@"H:|-%d-[tableView]-%d-|", 0, 0]];
    [constraints addObject:[NSString stringWithFormat:@"V:|-%d-[testButton(%d)]-%d-[tableView]-%d-|", 25, 44, 10, 0]];

    for (NSString *constraint in constraints) {
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:constraint options:0 metrics:nil views:views]];
    }

    for (UIView *view in self.view.subviews) {
        [view setTranslatesAutoresizingMaskIntoConstraints:NO];
    }
}


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    NSInteger sectionCount = _apples.count ? 1 : 0;
    sectionCount += _oranges.count ? 1 : 0;
    sectionCount += _grapes.count ? 1 : 0;
    return sectionCount ? sectionCount : 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section == 0) {
        return _apples.count ?: _oranges.count ?: _grapes.count ?: 0;
    }

    if (section == 1) {
        return _oranges.count ?: _grapes.count ?: 0;
    }

    return _grapes.count;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if (section == 0) {
        return _apples.count ? @"Apples" : (_oranges.count ? @"Oranges" : (_grapes.count ? @"Grapes" : @"No Fruits"));
    }

    if (section == 1) {
        if (_apples.count) {
            return _oranges.count ? @"Oranges" : (_grapes.count ? @"Grapes" : @"No Fruits");
        }
    }

    return @"Grapes";
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 60.0f;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 0) {
        Fruit fruit = _apples.count ? Apples : (_oranges.count ? Oranges : (_grapes.count ? Grapes : 0));
        return [self cellForFruit:fruit tableView:tableView indexPath:indexPath];
    }

    if (indexPath.section == 1) {
        if (_apples.count) {
            Fruit fruit = _oranges.count ? Oranges : (_grapes.count ? Grapes : 0);
            return [self cellForFruit:fruit tableView:tableView indexPath:indexPath];
        }
    }

    return [self cellForFruit:Grapes tableView:tableView indexPath:indexPath];
}

- (UITableViewCell *)cellForFruit:(Fruit)kind tableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:kImageCellIdentifier forIndexPath:indexPath];

    switch (kind) {
        case Apples: {
            NSString *kind = [_apples objectAtIndex:indexPath.row];

            [[cell imageView] setImage:nil]; //Some Image..
            [[cell textLabel] setText:kind];
        }
            break;

        case Oranges: {
            NSString *kind = [_oranges objectAtIndex:indexPath.row];

            [[cell imageView] setImage:nil]; //Some Image..
            [[cell textLabel] setText:kind];
        }
            break;

        case Grapes: {
            NSString *kind = [_grapes objectAtIndex:indexPath.row];

            [[cell imageView] setImage:nil]; //Some Image..
            [[cell textLabel] setText:kind];
        }
            break;

        default:
            break;
    }
    return cell;
}

- (void)loadData {
    [_apples removeAllObjects];
    [_oranges removeAllObjects];
    [_grapes removeAllObjects];

    dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    [self getApples:^(NSArray *apples) {

        if (apples.count) {
            @synchronized (self.apples) {
                [_apples addObjectsFromArray:apples];
            }
        }

        dispatch_group_leave(group);
    }];

    dispatch_group_enter(group);
    [self getOranges:^(NSArray *oranges) {

        if (oranges.count) {
            @synchronized (self.oranges) {
                [_oranges addObjectsFromArray:oranges];
            }
        }

        dispatch_group_leave(group);
    }];

    dispatch_group_enter(group);
    [self getGrapes:^(NSArray *grapes) {

        if (grapes.count) {
            @synchronized (self.grapes) {
                [_grapes addObjectsFromArray:grapes];
            }
        }

        dispatch_group_leave(group);
    }];

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        [self.tableView reloadData];
    });
}






//Simulating server calls..

- (void)getApples:(void(^)(NSArray *apples))completion {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSArray *apples = @[@"Red Apple", @"Sweet Apple", @"Sour Apple"];

        bool error = arc4random_uniform(100) <= 50;
        if (error) {
            completion(nil);
        }
        else {
            completion(apples);
        }
    });
}

- (void)getOranges:(void(^)(NSArray *oranges))completion {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSArray *apples = @[@"Tiny Orange", @"Bruised Orange", @"Red Orange"];

        bool error = arc4random_uniform(100) <= 50;
        if (error) {
            completion(nil);
        }
        else {
            completion(apples);
        }
    });
}

- (void)getGrapes:(void(^)(NSArray *grapes))completion {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSArray *apples = @[@"Baby Grapes", @"Green Grapes", @"I ran out of ideas for names.. Grapes"];

        bool error = arc4random_uniform(100) <= 50;
        if (error) {
            completion(nil);
        }
        else {
            completion(apples);
        }
    });
}

@end

http://i.imgur.com/S1DiqGW.png

enter image description here