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
}