Take action when two separate NSFetchRequests have both completed
Asked Answered
V

1

2

I'm using a remote database with Core Data and when I execute the following fetch requests, depending on the internet connection, it can take some time. I'd like to monitor these two requests and, when they are complete -- whether successful or failed -- I'd like to trigger another method.

FetchRequest 1:

 [self.managedObjectContext executeFetchRequest:fetchRequest1 onSuccess:^(NSArray *results) {
        //Succcess
        [self.refreshControl endRefreshing];

    } onFailure:^(NSError *error) {
        [self.refreshControl endRefreshing];
    }];

FetchRequest 2:

 [self.managedObjectContext executeFetchRequest:fetchRequest2 onSuccess:^(NSArray *results) {
        //Succcess
        [self.refreshControl endRefreshing];

    } onFailure:^(NSError *error) {
        [self.refreshControl endRefreshing];
    }];

I would like to wait until fetch requests 1 and 2 are both complete before calling another method.

Can I use NSOperationQueue to monitor both blocks? If not, what's the best way to know when both blocks have completed?

Vey answered 12/11, 2013 at 2:25 Comment(1)
check this thread #11910129Marijuana
G
4

When you have asynchronous tasks with dependencies, you have a couple of options:

  1. The simplest solution with the least code changes is to use a semaphore:

    // create a semaphore
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    // initiate two requests (signaling when done)
    
    [self.managedObjectContext executeFetchRequest:fetchRequest1 onSuccess:^(NSArray *results) {
        [self.refreshControl endRefreshing];
        dispatch_semaphore_signal(semaphore);
    } onFailure:^(NSError *error) {
        [self.refreshControl endRefreshing];
        dispatch_semaphore_signal(semaphore);
    }];
    
    [self.managedObjectContext executeFetchRequest:fetchRequest2 onSuccess:^(NSArray *results) {
        [self.refreshControl endRefreshing];
        dispatch_semaphore_signal(semaphore);
    } onFailure:^(NSError *error) {
        [self.refreshControl endRefreshing];
        dispatch_semaphore_signal(semaphore);
    }];
    
    // now create task to to wait for these two to finish signal the semaphore
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
        // wait for the two signals from the two fetches to be sent
    
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
        // now do whatever you want when those two requests finish
    
        // if you need to do any UI update or do any synchronizing with the main queue, just dispatch this to the main queue
    
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"all done");
        });
    });
    

    This approach might be easiest, but entails all sorts of limitations. For example, this will tie up a worker thread waiting for the other two to send the signal, so you'd have to be confident that you don't have too many of these collections of requests going concurrently. You also have to be confident that these requests will call either onSuccess or onFailure, but never both and always one. This also doesn't really offer an cancellation opportunities or the ability to constrain the degree of concurrency yourself. But you can do the above with minimal code changes.

  2. The second approach would be to replace your asynchronous requests with synchronous ones, that you can then use the NSOperation standard addDependency logic:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
        // completion operation
    }];
    
    NSOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        // do fetch1 _synchronously_
    }];
    
    [queue addOperation:operation1];
    
    NSOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        // do fetch2 _synchronously_
    }];
    
    [queue addOperation:operation2];
    
    [completionOperation addDependencies:@[operation1, operation2]];
    [queue addOperation:completionOperation];
    

    This approach requires that your synchronous fetches are thread-safe. I'm not familiar with this API you're using, so I can't speak to that.

  3. If you don't have synchronous renditions of your fetch requests that you could add to the queue, the third approach would be to wrap your asynchronous fetch requests with your own concurrent NSOperation subclass that won't signal isFinished until the asynchronous operation is done (and also presumably call your own onSuccess and onFailure blocks). Once you do that, you can then use the setDependency functionality (as illustrated in the prior point) to make your third operation dependent upon the other two finishing. For more information, see Configuring Operations for Concurrent Execution section of the Concurrency Programming Guide.

I wish I could provide a more definitive answer, but I'm not familiar enough with the options/constraints entailed by your concurrent managed context library.

Gradely answered 12/11, 2013 at 5:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.