NSBlockOperation, NSOperationQueue and Blocks
Asked Answered
F

1

6

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 ?

Floristic answered 29/8, 2014 at 17:33 Comment(2)
Have you tried to make NSOperationQueue an instance variable of the enclosing class?Ol
I did. I declared as instance variable and I instantiated it in ViewDidLoad. Nothing changed.Floristic
B
5

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.

Boots answered 29/8, 2014 at 22:18 Comment(2)
I figured out that only possible solution is to subclass NSOperation and manage manually two types of asynchronous block but I thought there was another "light" solution. I mark as valid your answer and soon as possible I'll post my implementation. ThxFloristic
A "lighter" solution might be to use Grand Central Dispatch's dispatch queues (NSOperation and family use those in their implementation). Along with the functions to create queues and dispatch to them, you'd also want to look at dispatch groups to do your dependency management. Dispatch queues are very powerful and light, but you would lose some nice functionality built in to NSOperation.Boots

© 2022 - 2024 — McMap. All rights reserved.