-1

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.

Troy
  • 961
  • 6
  • 13
  • 24
  • Do you access jsonArray in the other methods *before* it has been set from the background thread? – Martin R Aug 22 '13 at 04:35
  • Btw: Does fetchedData return an array or a dictionary? – Martin R Aug 22 '13 at 04:38
  • It returns a dictionary... sorry, this is from my PHP experience, where a Multidimensional Array = Dictionary – Troy Aug 22 '13 at 04:44
  • @martinR No, it's only after the queue runs to I access jsonArray. It's loading the returning data into a UITableView – Troy Aug 22 '13 at 04:50
  • Are you sure? The queue runs asynchronously in the background, so other data source methods such as numberOfRows:inSection: are probably called before the queue has finished. – Martin R Aug 22 '13 at 04:58
  • I think there's some confusion. I need to access jsonArray only after the queue has run. If you think there's a better way to approach it, I'm all for it :) – Troy Aug 22 '13 at 05:02
  • why dont u use synchronous way? – Master Stroke Aug 22 '13 at 05:08
  • 1
    Perhaps add more code and show how and when the dictionary/array is accessed. How do you know when the queue has run? - I would expect a `reloadData` call in `fetchedData:`. – Martin R Aug 22 '13 at 05:30
  • Thanks Martin, I'll add more. – Troy Aug 22 '13 at 14:25
  • @MartinR I just added more code. I'm also editing the main question to clarify what I'm trying to accomplish. – Troy Aug 22 '13 at 14:41

3 Answers3

0

Try to call the other method(which is using your jsonarray) through nsnotification...I am not sure there might some other ideas/ways of doing this.But i am presenting what i have in my mind.

Put this code inside your fetchedData method,

NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
            [nc addObserver:self selector:@selector(someMethod:) name:@"JSonDownloaded" object: jsonArray];
             [[NSNotificationCenter defaultCenter] postNotificationName:@"JSonDownloaded" object: jsonArray];

-(void)someMethod:(NSNotification *)nspk
{
    NSLog(@"%@",nspk.object);
//Only after this you can able to access the jsonArray.
}

Don't forget to unregister the observer.

Master Stroke
  • 5,108
  • 2
  • 26
  • 57
  • But, how does this solve my global dictionary issue? I need to use this dictionary in other methods. – Troy Aug 22 '13 at 04:48
  • @Troy..sorry i took your question in some otherway...now i have edited the answer...why dont use synchronous way to achive this? – Master Stroke Aug 22 '13 at 05:11
0

jsonArray is just an instance variable, but not a property. Thus, assigning an object to it does not retain it, and the object may be released as soon as the program returns to the run loop.
I suggest replacing the iVar by @property (strong) NSMutableDictionary *jsonArray; and @synthesize jsonArray;, and assigning the object to it by self.jsonArray = ...
EDIT (see comment of Martin R below):
Thus, if you are not using ARC, assigning an object to it does not retain it, and the object may be released as soon as the program returns to the run loop.
In this case, I suggest replacing the iVar by @property (retain) NSMutableDictionary *jsonArray; and @synthesize jsonArray;, and assigning the object to it by self.jsonArray = ...

Reinhard Männer
  • 14,022
  • 5
  • 54
  • 116
  • That would only make a difference if OP is still using manual reference counting. With ARC, instance variables are strong by default. – Martin R Aug 22 '13 at 05:26
  • @ReinhardMänner, I am using ARC. So, would I keep it the same? – Troy Aug 22 '13 at 14:41
  • @Troy: According to Martin R, this is not the problem if you are using ARC (I did not know), so the problem must be somewhere else. – Reinhard Männer Aug 22 '13 at 19:21
0

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.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks! I still couldn't get the array to work properly, so I ended up going a different route based on your inspiration. I put the queue into a method and am running each item from the dictionary as a global variable. Might be better this way anyways, but thanks for your help! – Troy Aug 23 '13 at 18:27