Reputation: 12819
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
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