NSBlockOperation, NSOperationQueue and Blocks

2.3k views Asked by At

I have to sync a bunch of information from my RestAPI. I must do 6 RestAPI calls to complete work. I designed API calls with Blocks, and return NSError if there is any. 3 of these calls should to execute nested because the first call gives information to others and allows the execution while the other 3 calls can run independently. Due to improve network performance, I designed my synchronization call as following:

  • 1 NSBlockOperation that contains the first nested 3 blocks;
  • 1 NSBlockOperation that contains other three blocks;
  • 1 NSBlockOperation that I use as "semphore" and tells me when all work done.

Last NSBlockOperation has dependency to previous two NSBlockOperation.

I also have a NSOperationQueue that contains all three NSBlockOperation where the semaphore NSBlockOperation is added as last in the queue. The result that I would to achieve is: first two blocks called Concurrently and when their work finish, the semaphore NSBlockOperation is called and returns controls to User providing UIAlertMessage.

The result isn't that previously explained: controls are returned without waiting the end of syncAllBlocksInformation block.

Below the code that contains NSBlockOperation:

-(void)syncAllBlocksInformation:(void(^)(NSError *error))completion{

__block NSError *blockError = nil;

NSOperation *syncUserInfoOperation = [NSBlockOperation blockOperationWithBlock:^{
    [dataSync syncUserInfo:tfMail.text password:tfPassword.text completion:^(NSError *error, NSNumber *idUser) {
        if(!error){
            [dataSync syncUserfilesInfo:idUser completion:^(NSError *error) {
                if(!error){
                    [dataSync syncUserBookings:^(NSError *error) {
                        if(error){
                            blockError = error;
                        }
                    }];
                }
                else{
                    blockError = error;
                }
            }];

        }
        else{
            blockError = error;
        }
    }];
}];



NSBlockOperation *otherSyncOperations = [NSBlockOperation blockOperationWithBlock:^{
    [dataSync syncNewsInfo:^(NSError *error) {
        if(error){
            blockError = error;
            NSLog(@"error %@",error);
        }
    }];

}];

[otherSyncOperations addExecutionBlock:^{
    [dataSync syncLocationsInfo:^(NSError *error) {
        if(error){
            blockError = error;
            NSLog(@"error %@",error);
        }
    }];

}];

[otherSyncOperations addExecutionBlock:^{
    [dataSync syncExoticAnimalTypesAndAnimals:^(NSError *error) {
        if(error){
            blockError = error;
            NSLog(@"error %@",error);
        }
    }];
}];


NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"END");
}];

[completionOperation setCompletionBlock:^{
    NSLog(@"Syc isEx %i",syncUserInfoOperation.isExecuting);
    NSLog(@"other isEx %i",otherSyncOperations.isExecuting);
    completion(blockError);
}];

NSOperationQueue *opQueue = [NSOperationQueue new];

[completionOperation addDependency:syncUserInfoOperation];
[completionOperation addDependency:otherSyncOperations];

[opQueue addOperation:syncUserInfoOperation];
[opQueue addOperation:otherSyncOperations];
[opQueue addOperation:completionOperation];

}

And here, code that calls above block:

-(IBAction)login:(id)sender{

[self dismissKeyboardOpened:nil];

hud=[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[hud setLabelText:NSLocalizedString(@"login_hud_message", login_hud_message )];
[hud setMode:MBProgressHUDModeIndeterminate];

[self showHudAndNetworkActivity:YES];

[self syncAllBlocksInformation:^(NSError *error) {

    [self showHudAndNetworkActivity:NO];

    if(!error){
        NSLog(@"End LOGIN");
        [self showAlert:@"Login" message:@"Login OK" dismiss:YES];
    }
    else{
        [self showAlert:@"Error" message:@"Login NO" dismiss:NO];
    }

}];
}

What's wrong ?

1

There are 1 answers

2
Mike S On BEST ANSWER

The issue is that NSBlockOperation is for synchronous blocks. It will be finished as soon as its block(s) have finished executing. If its block(s) fire off asynchronous methods, those will run independently.

For example, when your syncUserInfoOperation's block is executed, it fires off [dataSync syncUserInfo:...] and then considers itself done; it doesn't wait for any of the completion handlers to fire, or anything like that.

A good solution to this is to create your own NSOperation subclasses. You'd probably want to create one for each of your data sync types to make it easier to setup dependencies, etc., but that's up to you. You can read all about how to do that here (be sure to read the section on "Configuring Operations for Concurrent Execution").

You could also make a generic NSOperation subclass that takes a block that can be run asynchronously. The main issue with that is it makes it much harder to handle things like canceling the operation, which you probably still want.