iosobjective-cnsoperationnsoperationqueuensblockoperation

Not understanding NSOperationQueue sequence


I have a model class called DataFetcher that fetches data via a web service, then persists the data to a Core Data datastore, then updates a ViewController via delegate methods. Here is the normal sequence (which works properly) without using NSOperation:

NSArray *serviceQueryResult = [self queryServiceFor:@"car"];
[self setData:serviceQueryResult];
[self persistData:_data];
[_loadDelegate updateCount:_data.count]; 
[_loadDelegate receivedData:_data];

I need to place the web service call and the database update call on a background thread. My thoughts are to create an NSBlockOperation to make the web service call, then another NSBlockOperation to do the database update. There will be a dependency that the web service operation completes before the database update operation begins. Here is the code I am trying to implement:

__weak DataFetcher *weakSelf = self;
__block NSArray *serviceQUeryResult;
NSBlockOperation *webServiceOperation = [NSBlockOperation blockOperationWithBlock:^{
    serviceQUeryResult = [weakSelf queryServiceFor:@"mini"];
    [weakSelf setData:serviceQUeryResult];
}];
NSBlockOperation *dbInsertOperation = [NSBlockOperation blockOperationWithBlock:^{
    [weakSelf persistData:serviceQUeryResult];
}];

[webServiceOperation addDependency:dbInsertOperation];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:webServiceOperation];

When set up this way my queryServiceFor: method never gets called. I am also unsure of where to place the two delegate method calls since they update the UI and should be on the main thread. I have used GCD several times in the past, but now will be needing some of the extra functionality of NSOperations. Can anyone help? Thanks!


Solution

  • The fundamental issue is that you've declared webServiceOperation to be dependent upon dbInsertOperation (i.e. it won't start webServiceOperation until dbInsertOperation finishes), but you never start dbInsertOperation, so webServiceOperation will never run.

    1. If you want to make dbInsertOperation dependent upon webServiceOperation, you don't want:

      [webServiceOperation addDependency:dbInsertOperation];
      

      You instead want:

      [dbInsertOperation addDependency:webServiceOperation];
      
    2. Once you've created this dependency, make sure to add both of these operations to your queue.

      [queue addOperation:webServiceOperation];
      [queue addOperation:dbInsertOperation];
      

      The dependency will ensure that dbInsertOperation won't start until webServiceOperation finishes. Note, this assumes that webServiceOperation performs its block synchronously. If the network request runs asynchronously, you might want to wrap it in its own concurrent/asynchronous NSOperation subclass.


    If you want to update the UI from these background operations, you can either:

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // update the UI here
    }];
    

    or use GCD, if you want:

    dispatch_async(dispatch_get_main_queue(), ^{
        // update the UI here
    });