iosobjective-cmultithreadingnsinvocationoperation

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

Solution

  • 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
    }