A CountDownLatch with GCD

Published on — Filed under protip, cocoade

This project I'm working on has a lot of heavy animation activity going on simultaneously and often I find the need to trigger the next steps after all animations reach a synchronization point.

The problem

Execute a given block of code once all the animations complete — that is, when the last completion block of all the animations is called, the target block must be executed.

An elegant solution

My take on it was to create a block that executes synchronously — using GCD's dispatch_sync() function — that after N calls, triggers an internal block, the zeroBlock.

Here's some code:

@interface BBCountdownLatchFactory : NSObject
+ (void (^)())latchWithCounter:(NSUInteger)counter
               whenReachesZero:(void (^)())zeroBlock;
@end

@implementation BBCountdownLatchFactory
+ (void (^)())latchWithCounter:(NSUInteger)counter
               whenReachesZero:(void (^)())zeroBlock {
  __block NSUInteger toComplete = counter;
  void (^latch)() = ^() {
    // Grab a global queue; override with one of your own
    // queues if you prefer...
    dispatch_queue_t queue;
    queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_sync(queue, ^() {
      if (--toComplete == 0) {
        // When 'counter' calls are made to this block, call zeroBlock
        zeroBlock();
      }
    });
  };

  return latch;
}
@end

How do you use it? Easy as pie:

// Assume you have a NSArray with views to animate, _viewsToAnimate

void (^latch)() = [BBCountdownLatchFactory
                   latchWithCounter:[_viewsToAnimate count]
                   whenReachesZero:^() {
  NSLog(@"I'm done!");
  // Code to execute when all animations are done
}];

for (UIView* view in _viewsToAnimate) {
  [UIView animateWithDuration:0.3 animations:^() {
    // Some fancy animations on 'view'
  } completion:^(BOOL finished) {
    // Every time an animation completes, call the latch block.
    // The last animation to complete will unlock the latch and execute
    // the code inside the zeroBlock.
    latch();
  }];
}

ProTip™

If you only use this on UIView completion blocks, you may want to change the signature of the zeroBlock to include a BOOL parameter:

@interface BBCountdownLatchFactory : NSObject
+ (void (^)())latchWithCounter:(NSUInteger)counter
               whenReachesZero:(void (^)(BOOL))zeroBlock;
@end

This will allow you to use it directly on UIView's animateWithDuration:animations:completion: method.

[UIView animateWithDuration:0.3 animations:^() {
  // Some fancy animations
} completion:latch];