kpce
kpce

Reputation: 35

Adding information returned by threads to an array

I am relatively new to Objective C and I am trying to read information from a URL then add its info to my Name object then add the name objects to an array. I am able to read the info from the URLs fine and furthermore I am successful in adding their info to an array however I cannot seem to successfully add the names to my nameArray. I declare the array at the top and initialize it in the viewDidLoad function then furthermore the names are meant to be added to the array when I call the methods readURLOne, readURLTwo and readURLThree. However, when I check the array, after calling these methods in viewDidLoad, it is empty. Why might this be?

@interface ViewController () {
    Name *n1;
    Name *n2;
    Name *n3;
    NSMutableArray *nameArray; //this is the array I am having trouble with
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    nameArray = [[NSMutableArray alloc] init];
    NSOperationQueue *queue = [NSOperationQueue new];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(readURLOne)object:nil];
    [queue addOperation:operation];

    operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(readURLTwo)object:nil];
    [queue addOperation:operation];

    operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(readURLThree) object:nil];
    [queue addOperation:operation];
    NSLog(@"NAME ARRAY: %lu", (unsigned long)[nameArray count]); //This is where I am trying to check if they are added to the array, the are not
} 

-(void) readURLOne {
    NSURL *dataURL = [NSURL URLWithString:@"http://example.org/data/1.txt"];
    NSArray *tmp = [NSArray arrayWithContentsOfURL:dataURL];
    for (NSString *str in tmp){ //add contents of URL successfully to tmp
        NSLog(@"%@", str);
    }
    n1 = [[Name alloc] init];
    [n1 setFirstName:tmp[0]];
    [n1 setLastName:tmp[1]];
    [n1 setNumber:tmp[2]];
    [nameArray addObject:n1];
 }

-(void) readURLTwo {
    NSURL *dataURL = [NSURL URLWithString:@"http://example.org/data/2.txt"];
    NSArray *tmp = [NSArray arrayWithContentsOfURL:dataURL];
    for (NSString *str in tmp){
        NSLog(@"%@", str);
    }
    n2 = [[Name alloc] init];
    [n2 setFirstName:tmp[0]];
    [n2 setLastName:tmp[1]];
    [n2 setNumber:tmp[2]];
    [nameArray addObject:n2];
}

-(void) readURLThree {
    NSURL *dataURL = [NSURL URLWithString:@"http://example.org/data/3.txt"];
    NSArray *tmp = [NSArray arrayWithContentsOfURL:dataURL];
    for (NSString *str in tmp){
        NSLog(@"%@", str);
    }
    n3 = [[Name alloc] init];
    [n3 setFirstName:tmp[0]];
    [n3 setLastName:tmp[1]];
    [n3 setNumber:tmp[2]];
    [nameArray addObject:n3];
}

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

-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return nameArray.count;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellIdentifier = @"Cell";
    CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [[CustomCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
    }

    cell.firstName.text = [[nameArray objectAtIndex:indexPath.row] getFirstName];
    cell.lastName.text = [[nameArray objectAtIndex:indexPath.row] getLastName];
    return cell;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

Upvotes: 0

Views: 43

Answers (2)

Ian MacDonald
Ian MacDonald

Reputation: 14030

NSOperationQueues operate asynchronously, so there's no guarantee that they will be complete by the time you're accessing nameArray.

You could add this line before your NSLog:

[queue waitUntilAllOperationsAreFinished];

But really, I think you should probably just call the functions directly without using an NSOperationQueue.


On further inspection of the methods you're calling, it looks like you're performing network operations. This should definitely be done on a different thread like you've already done, so you really just need to add a callback for the completion. You could do this pretty simply by adding a finish operation:

  NSInvocationOperation *completionOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(urlDataRead) object:nil];

  // ...
  [completionOperation addDependency:operation];
  // ...
  [completionOperation addDependency:operation];
  // ...
  [completionOperation addDependency:operation];

  [queue addOperation:completionOperation];
}

- (void)urlDataRead {
  NSLog(@"NAME ARRAY: %lu", (unsigned long)[nameArray count]); //This is where I am trying to check if they are added to the array, the are not
}

Upvotes: 0

Avt
Avt

Reputation: 17043

I declare the array at the top and initialize it in the viewDidLoad function then furthermore the names are meant to be added to the array when I call the methods readURLOne, readURLTwo and readURLThree.

As I can see you are NOT calling readURLOne, readURLTwo and readURLThree - you are adding them to NSOperationQueue. In such a case your methods are called asynchronously and nameArray is probably updated after you check it at the end of viewDidLoad.

Change your method to:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    nameArray = [[NSMutableArray alloc] init];
    NSOperationQueue *queue = [NSOperationQueue new];
    queue.maxConcurrentOperationCount = 1; // It is needed to avoid synchronisation problems with nameArray
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(readURLOne)object:nil];
    [queue addOperation:operation1];

    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(readURLTwo)object:nil];
    [queue addOperation:operation2];

    NSInvocationOperation *operation3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(readURLThree) object:nil];
    [queue addOperation:operation3];


    NSOperation *finalOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NAME ARRAY: %lu", (unsigned long)[nameArray count]);
    }];
    [finalOperation addDependency:operation1];
    [finalOperation addDependency:operation2];
    [finalOperation addDependency:operation3];
    [queue addOperation:finalOperation];
} 

Upvotes: 1

Related Questions