NSTimer is scheduled to thread’s runloop. In code of question, runloop of thread dispatched by GCD is not running. You must start it manually and there must be a way to exit run loop, so you should keep a reference to the NSTimer, and invalidate it in appropriate time.
NSTimer has strong reference to the target, so target can't has strong reference to timer, and runloop has strong reference to the timer.
weak var weakTimer: Timer?
func configurateTimerInBackgroundThread(){
DispatchQueue.global().async {
// Pause program execution in Xcode, you will find thread with this name
Thread.current.name = "BackgroundThreadWithTimer"
// This timer is scheduled to current run loop
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
// Start current runloop manually, otherwise NSTimer won't fire.
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
}
}
@objc func runTimer(){
NSLog("Timer is running in mainThread: \(Thread.isMainThread)")
}
If timer is invalidated in future, pause program execution again in Xcode, you will find that thread is gone.
Of course, threads dispatched by GCD have runloop. GCD generate and reuse threads internally, there threads are anonymous to caller. If you don't feel safe to it, you could use Thread. Don't afraid, code is very easy.
Actually, I try same thing last week and get same fail with asker, then I found this page. I try NSThread before I give up. It works. So why NSTimer in GCD can't work? It should be. Read runloop's document to know how NSTimer works.
Use NSThread to work with NSTimer:
func configurateTimerInBackgroundThread(){
let thread = Thread.init(target: self, selector: #selector(addTimerInBackground), object: nil)
thread.name = "BackgroundThreadWithTimer"
thread.start()
}
@objc func addTimerInBackground() {
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimer), userInfo: nil, repeats: true)
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
}