Skip to content

Instantly share code, notes, and snippets.

@swillits
Last active September 13, 2015 17:15
Show Gist options
  • Save swillits/f5ba9815e67488e27a1a to your computer and use it in GitHub Desktop.
Save swillits/f5ba9815e67488e27a1a to your computer and use it in GitHub Desktop.
//
// AGEnqueueCoalescedBlock.h
// EnqueuedBlocks
//
// Created by Seth Willits on 2/26/15.
// Copyright (c) 2015 Araelium Group. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Executes the block on the given queue immediately if no block with 'key' is running. Otherwise, this block is enqeued,
//! replacing any previously-enqueued block with the same 'key'. Once the running block finishes, the enqueued block executes (if there is one).
void AGEnqueueCoalescedBlock(void * key, dispatch_queue_t queue, void (^block)(void));
//! Removes an enqueued block with the given key, preventing it from running.
void AGCancelEnqueuedBlock(void * key);
//
// AGEnqueueCoalescedBlock.m
// EnqueuedBlocks
//
// Created by Seth Willits on 2/26/15.
// Copyright (c) 2015 Araelium Group. All rights reserved.
//
#import "AGEnqueueCoalescedBlock.h"
static void AGEnqueueCoalescedBlock_Init(void);
static void AGEnqueue_runEnqueuedBlockForKey(void * key);
static void AGEnqueue_doneRunningEnqueuedBlockForKey(void * key);
static dispatch_queue_t __AGEnqueueQueue;
static NSMutableDictionary * __AGEnqueueTable;
static NSMutableSet * __AGEnqueueRunningSet;
#pragma mark -
#pragma mark Public
void AGEnqueueCoalescedBlock(void * key, dispatch_queue_t queue, void (^block)(void))
{
if (key == NULL) [NSException raise:NSInvalidArgumentException format:@"AGEnqueueCoalescedBlock key cannot be AGAllEnqueuedBlocks"];
if (queue == nil) [NSException raise:NSInvalidArgumentException format:@"AGEnqueueCoalescedBlock queue cannot be nil"];
if (block == nil) [NSException raise:NSInvalidArgumentException format:@"AGEnqueueCoalescedBlock block cannot be nil"];
AGEnqueueCoalescedBlock_Init();
dispatch_sync(__AGEnqueueQueue, ^{
// Insert/Update entry
[__AGEnqueueTable setObject:@[[[block copy] autorelease, (id)queue]] forKey:[NSValue valueWithPointer:key]];
// Run if needed
BOOL running = [__AGEnqueueRunningSet containsObject:[NSValue valueWithPointer:key]];
if (!running) {
AGEnqueue_runEnqueuedBlockForKey(key);
}
});
}
void AGCancelEnqueuedBlock(void * key)
{
AGEnqueueCoalescedBlock_Init();
dispatch_sync(__AGEnqueueQueue, ^{
[__AGEnqueueTable removeObjectForKey:[NSValue valueWithPointer:key]];
});
}
#pragma mark -
#pragma mark Private
static void AGEnqueueCoalescedBlock_Init(void)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__AGEnqueueQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
__AGEnqueueTable = [[NSMutableDictionary alloc] init];
__AGEnqueueRunningSet = [[NSMutableSet alloc] init];
});
}
// Can only be called from __AGEnqueueQueue and assumes
// there is no block already running for the given tag
static void AGEnqueue_runEnqueuedBlockForKey(void * key)
{
NSArray * info = [[__AGEnqueueTable objectForKey:[NSValue valueWithPointer:key]] retain];
if (info) {
void (^block)(void) = [info objectAtIndex:0];
dispatch_queue_t queue = (dispatch_queue_t)[info objectAtIndex:1];
[__AGEnqueueTable removeObjectForKey:[NSValue valueWithPointer:key]];
[__AGEnqueueRunningSet addObject:[NSValue valueWithPointer:key]];
dispatch_async(queue, ^{
block();
AGEnqueue_doneRunningEnqueuedBlockForKey(key);
});
[info release];
}
}
// Could be called from any thread
static void AGEnqueue_doneRunningEnqueuedBlockForKey(void * key)
{
dispatch_sync(__AGEnqueueQueue, ^{
[__AGEnqueueRunningSet removeObject:[NSValue valueWithPointer:key]];
AGEnqueue_runEnqueuedBlockForKey(key);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment