Grand Central Dispatch alternative to using an NSTimer - invalidating multiple times

2.3k views Asked by At

I have an issue with a dispatch_source_t I'm trying to use. I'm wanting to use it to delay the processing of a PHChange for 5 seconds because a PHChange can happen multiple times in a short span. I'd appreciate any help offered. Essentially I want to cancel the prior dispatch_source_t timer almost like an NSTimer.

@property (nonatomic, strong) dispatch_source_t libraryChangedTimer;

dispatch_source_t CreateTimerDispatchSource(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block)
{
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

    if (timer)
    {
        dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);

        dispatch_source_set_event_handler(timer, block);

        dispatch_resume(timer);
    }

    return timer;
}

- (void)libraryChanged:(PHChange *)changeInstance
{
    NSLog(@"Called immediately and it shouldn't");
}

- (void)photoLibraryDidChange:(PHChange *)changeInstance
{
    if (self.libraryChangedTimer)
    {
        dispatch_source_cancel(self.libraryChangedTimer);

        self.libraryChangedTimer = CreateTimerDispatchSource(5ull * NSEC_PER_SEC, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^
        {
            [self libraryChanged:changeInstance];
            dispatch_source_cancel(self.libraryChangedTimer);
        });
    }
    else
    {
        self.libraryChangedTimer = CreateTimerDispatchSource(5ull * NSEC_PER_SEC, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^
        {
            [self libraryChanged:changeInstance];
            dispatch_source_cancel(self.libraryChangedTimer);
        });
    }
}
2

There are 2 answers

0
klcjr89 On BEST ANSWER

I figured this out quite simply with using dispatch_after Code:

dispatch_source_t CreateTimerDispatchSource(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block)
{
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

    if (timer)
    {
        dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), DISPATCH_TIME_FOREVER, leeway);

        dispatch_source_set_event_handler(timer, block);

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, interval), queue,^
        {
            dispatch_resume(timer);
        });
    }

    return timer;
}

- (void)libraryChanged:(PHChange *)changeInstance
{
    // Do something 
}

- (void)photoLibraryDidChange:(PHChange *)changeInstance
{
    if (self.libraryChangedTimer)
    {
        dispatch_source_cancel(self.libraryChangedTimer);

        self.libraryChangedTimer = CreateTimerDispatchSource(5ull * NSEC_PER_SEC, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^
        {
            [self libraryChanged:changeInstance];
            dispatch_source_cancel(self.libraryChangedTimer);
            self.libraryChangedTimer = nil;
        });
    }
    else
    {
        self.libraryChangedTimer = CreateTimerDispatchSource(5ull * NSEC_PER_SEC, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^
        {
            [self libraryChanged:changeInstance];
            dispatch_source_cancel(self.libraryChangedTimer);
            self.libraryChangedTimer = nil;
        });
    }
}
0
Ken Thomases On

Your problem is this line:

    dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);

You are specifying, in effect, "now" as the timer's start time. That's what dispatch_walltime(NULL, 0) calculates. You are passing your interval value as the timer's interval, which is asking it to repeat with that period between firings. But the start time determines the time of the first firing.

What you wanted was:

    dispatch_source_set_timer(timer, dispatch_walltime(NULL, interval), interval, leeway);

Or, if you don't actually want the timer to repeat:

    dispatch_source_set_timer(timer, dispatch_walltime(NULL, interval), DISPATCH_TIME_FOREVER, leeway);