Search code examples
objective-cnsmutabledictionarydispatch-async

Dispatch Queue and global NSMutableDictionary - Objective C


I'm trying to use a global NSMutableDictionary from a dispatch queue. However, the items keep coming back NULL.

What I'm trying to do is access an external json file with a dispatch_queue, then populate a UITableView with this info.

Here's what I have

vc.h:

@interface viewcontroller {
 NSMutableDictionary *jsonArray;
}

vc.m:

    #define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) //1
    #define jsonTest [NSURL URLWithString:@"http://www.sometest.com/test.php"]

    -(void)viewDidLoad {
      dispatch_async(kBgQueue, ^{
            NSData* data = [NSData dataWithContentsOfURL:
                            jsonTest];
           [self performSelectorOnMainThread:@selector(fetchedData:)
                                   withObject:data waitUntilDone:YES];
            // if I run the log here, I can access jsonArry and the log prints correctly
            NSLog(@"City: %@", [jsonArray objectForKey:@"city"];
        });
    }

    -(NSMutableDictionary *)fetchedData:(NSData *)responseData {

        NSError *error;
        jsonArray = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
        return jsonArray;
    }

/********************* Table formatting area **********************/

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if (tableView == self.ipTable) { 
        if ([ipArray count] == 0){
            return 1;
        } else { // meta table
            return [ipArray count];
        }
    } else { // IP Meta Data
        return [jsonArray count];
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{


    if (tableView == self.myTable) {
        NSString *CellIdentifier = NULL;
        if ([ipArray count] == 0) {
            CellIdentifier = @"No Cells";
        } else {
            CellIdentifier = @"IP Cell";
        }

        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

        if (cell == nil) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        }

        if ([ipArray count] == 0)
        {
            [cell.textLabel setText:NSLocalizedString(@"None Found", nil)];
            return cell;

        } else {

        IPAddr *theip = [ipArray objectAtIndex: [indexPath row]];
        NSString *theipname = [theip ipName];
        if ([theipname isEqualToString:@""]) {
            [cell.textLabel setText: [theip ipNum]];
            [cell.detailTextLabel setText:NSLocalizedString(@"noName", nil)];
        } else {
            [cell.textLabel setText: [theip ipName]];
            [cell.detailTextLabel setText: [theip ipNum]];
        }
        return cell;
        }

    } else { // meta table

        static NSString *CellIdentifier = @"metaCell";

        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

        if (cell == nil) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        }

        // jsonArray content would go here to fill the cells.
        /******************** something here to fill the cells using jsonArray ********************/
        return cell;
    }

} // END UITAbleViewCell

If I access the jsonArray inside the queue, it returns fine and prints the log for the city. However, if I try to use it outside the queue, it returns NULL.

I'm trying to figure out what is happening, any ideas?

I need to use jsonArray in different methods in the same view, so I need it to be global.


Solution

  • I am fairly sure that the problem is that the data source methods (numberOfRowsInSection, cellForRowAtIndexPath) are called before the background thread has finished and filled the jsonArray. Therefore you have to reload the table view when the background thread has finished:

    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        dispatch_async(kBgQueue, ^{
            NSData *data = [NSData dataWithContentsOfURL:jsonTest];
            NSError *error;
            NSArray *tmpArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
            dispatch_sync(dispatch_get_main_queue(), ^{
                // Assign new data to data source and reload the table view:
                jsonArray = tmpArray;
                [self.metaTableView reloadData];
            });
        });
    }
    

    So the table view would be empty initially, and reloaded later when the data has arrived.