KalyanChakravarthy.net

Thoughts, stories and ideas.

Dispatch Sources: Timer

Dispatch souces define an event source that when triggered can invoke a pre-defined callback. Instead of registering for events and then writing logic to handle events to make a callback, dispatch source can be used.

Dispatch timer

For example, with interval timers, its not trivial to setup NSTimer outside of main threads as it requires a run-loop with predictable lifetime. Using a runloop outside main thread is painful. Dispatch queues have ephimeral threads and as such are bad candidates for use with NSTimers.

This is a scenario where dispatch source is an ideal use-case. A dispatch timer source, fires an event when the time interval has been completed, which then fires a pre-set callback all on the same queue.

// Define a queue
queue = dispatch_queue_create("com.blah",0);
	
// Define timer
interval_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
	
// Configure timer with type(wallclock), interval(2 mins), leeway(5)
// leeway is acceptable error
dispatch_source_set_timer( interval_timer, dispatch_walltime(NULL,0), 2*60, 5 );
	
// Callback when timer is fired
dispatch_source_set_event_handler(interval_timer, ^{
	NSLog(@"Timer event");
});	

It is also easy to start and suspend timer events. Its as simple as calling

dispatch_resume( interval_timer );
dispatch_suspend( interval_timer );

Dispatch After

If an event needs to be fired after a time interval, but does not require cancellation, it is simpler to just use dispatch_after

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<delayInSeconds> * NSEC_PER_SEC)), <queue>, ^{
    // code to be executed after a specified delay
});

Example: Flushing Queue after time interval

Here is some pseudo/sample code that consumes objects via -addEvent: until - either the object count becomes <N>, or <T> seconds have elapsed since last object add and in the end calls -flush:

@implementation KCTimer
{
	dispatch_source_t _interval_timer;
	dispatch_queue_t _flush_queue;
	NSUInteger _count;
	NSUInteger _timelimit;
}

- (instancetype) initWithCount:(NSUInteger)count andTimeLimit:(NSUInteger)limit 
{
	if( self = [super init] ) {
		_timelimit = limit;
		_count = count;

		_flush_queue = dispatch_queue_create("com.blah",0);
		_interval_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, flush_queue);
		dispatch_source_set_event_handler(_interval_timer, ^{
			[self flush];
		});	
		[self reset];
		[self resume];
	}
	return self;
}

- (void)resume
{
	dispatch_resume( _interval_timer );
}

- (void)suspend
{
	dispatch_suspend( _interval_timer );
}

- (void)reset
{
	dispatch_source_set_timer( interval_timer, dispatch_walltime(NULL,0), _timelimit, 5 );
}

- (void)addEvent:(Event *)e 
{
	// TODO: Add stuff in thread safe manner
	[self reset];
	if(eventCount >= 50) {
		dispatch_async(flush_queue,^{
			[self flush];
		});
	}
}

- (void)flush
{
	// TODO: Flush queue in thread safe manner
}