Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Move Timer to Background Thread

I currently have two NSTimer timers in my app that are responsible for taking data and updating the UI. I have noticed recently that while the timers are running I have poor UI performance, for example the user can't scroll through my UITableview well or at all. I've read elsewhere that it is possible to push the timers into a different runloop and that this could help. This is what my timer looks like now:

let aSelector : Selector = "updateLocation"
timer = NSTimer.scheduledTimerWithTimeInterval(((NSUserDefaults.standardUserDefaults().stringForKey("mapUpdate")! as NSString).doubleValue), target: self, selector: aSelector, userInfo: nil, repeats: true)

How can I modify my Swift timer such that it runs in a background thread? I've been reading about it but have been getting confused.

like image 711
user3185748 Avatar asked Jan 08 '15 12:01

user3185748


People also ask

Does timer run on main thread?

If a timer is fired on the main thread's run loop, you will probably have a problem with either the timer function or UI operation. For example, suppose you have triggered a timer. There is also a tableView in your app that shows a list to the user and the user is scrolling the table.

How do I make a countdown timer in Swift?

Creating a non-repeating timerlet timer1 = Timer. scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: false) let timer2 = Timer. scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in print("Timer fired!") }


3 Answers

If a timer is fired on the main thread's run loop, you will probably have a problem with either the timer function or UI operation.

For example, suppose you have triggered a timer. There is also a tableView in your app that shows a list to the user and the user is scrolling the table. Meanwhile, the timer's time elapses. We expect that the code block of the timer executes immediately, but it doesn't until the user ends the scroll operation. In many cases, this isn't what we want.

The source of this problem and the problem you mentioned in your question is the fact that you have run the timer on the main thread, where your task and all UI operations are handled serially. serial means one task is executed only if the previous task finished.

To solve that, one solution is to call timers on a background thread's run loop like below:

DispatchQueue.global(qos: .background).async {
        Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { _ in
            print("After 3 seconds in the background")
        }
        RunLoop.current.run()
    }

The scheduledTimer method creates a timer and schedules it on the current run loop. The following code has the same meaning:

DispatchQueue.global(qos: .background).async {
        let timer = Timer(timeInterval: 6, repeats: false) { _ in
            print("After 6 seconds in the background")
        }
        let runLoop = RunLoop.current
        runLoop.add(timer, forMode: .default)
        runLoop.run()
    }

Notice: if your timer is called repeatedly, don't forget to call invalidate() of the timer at the end of the task, otherwise the run loop will keep a strong reference to the target object of the timer that could result in memory leak.

like image 140
Justin Avatar answered Sep 22 '22 03:09

Justin


Updated in swift5

RunLoop.main.add(timer, forMode: RunLoop.Mode.common)
like image 44
Nrv Avatar answered Sep 23 '22 03:09

Nrv


To schedule a timer on a different run loop, you can use:

// Create an unscheduled timer
let timer = NSTimer(
    ((NSUserDefaults.standardUserDefaults().stringForKey("mapUpdate")! as NSString).doubleValue),
    target: self,
    selector: "updateLocation",
    userInfo: nil,
    repeats: true)

// Add the timer to a runloop (in this case the main run loop)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)

Note that I'm not sure that this actually solves your problem, or even that it would be a good way of handling it. From your further discussion I think I'd probably focus on something that used CLLocationManager.startMonitoringSignificantLocationChanges which would remove the need to periodically check location at all, just update when it's changed significantly, where significantly is appropriately defined. Note that speed and course are available directly from the CLLocation so it's not really important that it be updated at precise intervals.

like image 28
David Berry Avatar answered Sep 25 '22 03:09

David Berry