I have a task that needs to be performed every 1 second. Currently I have an NSTimer firing repeatedly every 1 sec. How do I have the timer fire in a background thread (no
Today after 6 years, I try to do same thing, here is alternative soltion: GCD or NSThread.
Timers work in conjunction with run loops, a thread's runloop can be get from the thread only, so the key is that schedule timer in the thread.
Except main thread's runloop, runloop should start manually; there should be some events to handle in running runloop, like Timer, otherwise runloop will exit, and we can use this to exit a runloop if timer is the only event source: invalidate the timer.
The following code is Swift 4:
weak var weakTimer: Timer?
@objc func timerMethod() {
// vefiry whether timer is fired in background thread
NSLog("It's called from main thread: \(Thread.isMainThread)")
}
func scheduleTimerInBackgroundThread(){
DispatchQueue.global().async(execute: {
//This method schedules timer to current runloop.
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
//start runloop manually, otherwise timer won't fire
//add timer before run, otherwise runloop find there's nothing to do and exit directly.
RunLoop.current.run()
})
}
Timer has strong reference to target, and runloop has strong reference to timer, after timer invalidate, it release target, so keep weak reference to it in target and invalidate it in appropriate time to exit runloop(and then exit thread).
Note: as an optimization, syncfunction of DispatchQueue invokes the block on the current thread when possible. Actually, you execute above code in main thread, Timer is fired in main thread, so don't use sync function, otherwise timer is not fired at the thread you want.
You could name thread to track its activity by pausing program executing in Xcode. In GCD, use:
Thread.current.name = "ThreadWithTimer"
We could use NSThread directly. Don't afraid, code is easy.
func configurateTimerInBackgroundThread(){
// Don't worry, thread won't be recycled after this method return.
// Of course, it must be started.
let thread = Thread.init(target: self, selector: #selector(addTimer), object: nil)
thread.start()
}
@objc func addTimer() {
weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
RunLoop.current.run()
}
If you want to use Thread subclass:
class TimerThread: Thread {
var timer: Timer
init(timer: Timer) {
self.timer = timer
super.init()
}
override func main() {
RunLoop.current.add(timer, forMode: .defaultRunLoopMode)
RunLoop.current.run()
}
}
Note: don't add timer in init, otherwise, timer is add to init's caller's thread's runloop, not this thread's runloop, e.g., you run following code in main thread, if TimerThread add timer in init method, timer will be scheduled to main thread's runloop, not timerThread's runloop. You can verify it in timerMethod() log.
let timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
weakTimer = timer
let timerThread = TimerThread.init(timer: timer)
timerThread.start()
P.S About Runloop.current.run(), its document suggest don't call this method if we want runloop to terminate, use run(mode: RunLoopMode, before limitDate: Date), actually run() repeatedly invoke this method in the NSDefaultRunloopMode, what's mode? More details in runloop and thread.