How do I create a NSTimer on a background thread?

If you need this so timers still run when you scroll your views (or maps), you need to schedule them on different run loop mode. Replace your current timer:

[NSTimer scheduledTimerWithTimeInterval:0.5
                                 target:self
                               selector:@selector(timerFired:)
                               userInfo:nil repeats:YES];

With this one:

NSTimer *timer = [NSTimer timerWithTimeInterval:0.5
                                           target:self
                                         selector:@selector(timerFired:)
                                         userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

For details, check this blog post: Event tracking stops NSTimer

EDIT : second block of code, the NSTimer still runs on the main thread, still on the same run loop as the scrollviews. The difference is the run loop mode. Check the blog post for a clear explanation.


If you want to go pure GCD and use a dispatch source, Apple has some sample code for this in their Concurrency Programming Guide:

dispatch_source_t CreateDispatchTimer(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;
}

Swift 3:

func createDispatchTimer(interval: DispatchTimeInterval,
                         leeway: DispatchTimeInterval,
                         queue: DispatchQueue,
                         block: @escaping ()->()) -> DispatchSourceTimer {
    let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: 0),
                                               queue: queue)
    timer.scheduleRepeating(deadline: DispatchTime.now(),
                            interval: interval,
                            leeway: leeway)

    // Use DispatchWorkItem for compatibility with iOS 9. Since iOS 10 you can use DispatchSourceHandler
    let workItem = DispatchWorkItem(block: block)
    timer.setEventHandler(handler: workItem)
    timer.resume()
    return timer
}

You could then set up your one-second timer event using code like the following:

dispatch_source_t newTimer = CreateDispatchTimer(1ull * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // Repeating task
});

making sure to store and release your timer when done, of course. The above gives you a 1/10th second leeway on the firing of these events, which you could tighten up if you desired.


The timer would need to be installed into a run loop operating on an already-running background thread. That thread would have to continue to run the run loop to have the timer actually fire. And for that background thread to continue being able to fire other timer events, it would need to spawn a new thread to actually handle events anyway (assuming, of course, that the processing you're doing takes a significant amount of time).

For whatever it's worth, I think handling timer events by spawning a new thread using Grand Central Dispatch or NSBlockOperation is a perfectly reasonable use of your main thread.


This should work,

It repeats a method every 1 second in a background queue without using NSTimers :)

- (void)methodToRepeatEveryOneSecond
{
    // Do your thing here

    // Call this method again using GCD 
    dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    double delayInSeconds = 1.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, q_background, ^(void){
        [self methodToRepeatEveryOneSecond];
    });
}

If you are in the main queue and you want to call above method you could do this so it changes to a background queue before is run :)

dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(q_background, ^{
    [self methodToRepeatEveryOneSecond];
});

Hope it helps