bcattle
bcattle

Reputation: 12819

Objective-C wait for asynchronous operation and enqueue completion handlers

I have to calculate a costly value. After this value is computed, I'd like to run a completion handler block:

-(void) performCostlyCalculationWithCompletionHandler:(void (^)(void)complete 
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        id result = [self costlyCalculation];
        dispatch_async(dispatch_get_main_queue(), ^{
            complete(result);
        });
    });
}

Pretty standard.

Now, I'd like to be able to call this function repeatedly, without re-enqueueing costlyCalculation. If costlyCalculation is already running I'd like to just save the completion blocks and call them all with the same result once costlyCalculation finishes.

Is there a simple way to do this with GCD or an NSOperationQueue? Or should I just store the completion blocks in an NSArray and call them myself? If I do this, what sort of synchronization do I need to put around this array?

UPDATE

I'm able to get close with dispatch_group_notify. Basically, I can enqueue the work blocks and enqueue all completion handlers to run after the group:

dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^(){
    // Do something that takes a while
    id result = [self costlyCalculation];
    dispatch_group_async(group, dispatch_get_main_queue(), ^(){
        self.result = result;
    });
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
    complete(result);
});

This would work, but how can I tell if costlyCalcuation is already running and not enqueue the work if I don't need to?

Upvotes: 4

Views: 1553

Answers (1)

Wonjae
Wonjae

Reputation: 309

I think you've mostly solved the problem already. I just came up with an alternative using NSOperationQueue and dependency between NSOperations. Here's a pseudo code I think.

// somewhere, create operation queue
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];

-(void)tryCalculation:(CompletionBlockType)completionBlock
{
    if(opQueue.operationCount > 0)
    {
        NSOperation *op = [[NSOperation alloc] init];
        op.completionBlock = completionBlock;
        // you can control how to synchronize completion blocks by changing dependency object. In this example, all operation will be triggered at once when costly calculation finishes
        [op addDependency:[opQueue.operations firstObject]];
        [opQueue addOperation:op];
    }
    else
    {
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(costlyCalculation) object:nil];
        op.completionBlock = completionBlock;
        [opQueue addOperation:op];
    }
}

Still, there can be subtle timing issue. Maybe we could use additional flag in costlyCalculation function.

Upvotes: 1

Related Questions