NSTimer è pianificato per il runloop del thread. Nel codice della domanda, runloop di thread inviato da GCD non è in esecuzione. È necessario avviarlo manualmente e deve esserci un modo per uscire dal ciclo di esecuzione, quindi è necessario mantenere un riferimento a NSTimer e invalidarlo nel momento opportuno.
NSTimer ha forte riferimento al target, in modo bersaglio non può ha forte riferimento al timer e runloop ha forte riferimento al 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)")
}
Se il timer viene invalidato in futuro, sospendere nuovamente l'esecuzione del programma in Xcode, troverete che il thread è andato.
Naturalmente, i thread inviati da GCD hanno un runloop. GCD genera e riutilizza i thread internamente, i thread sono anonimi per il chiamante. Se non ti senti sicuro, puoi usare Thread. Non aver paura, il codice è molto semplice.
In realtà, ho provato la stessa cosa la scorsa settimana e ho avuto lo stesso esito con il richiedente, quindi ho trovato questa pagina. Provo NSThread prima di arrendermi. Funziona. Quindi perché NSTimer in GCD non può funzionare? Dovrebbe essere. Leggi runloop's document per sapere come funziona NSTimer.
Usa NSThread di lavorare con 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)
}
un'altra alternativa in seguito secondo la risposta di Kevin, ho risolto i miei problemi sostituendo il NSTimer con MSWeakTimer (github.com/mindsnacks/MSWeakTimer), e appena superato il dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) come dispatchQueue. –
Ottimo riassunto. Grazie fantastico. Se ho capito bene: 1. Stai dicendo che nessun thread di invio GCD ha un ciclo di esecuzione ** eccetto ** per il thread principale? 2. Se è vero, allora che ne dici di dispatch_after? Come si processerebbe senza un runloop? Oppure è implementato anche in un meccanismo di conoscenza GCD? – Honey
@Honey I thread di invio non hanno cicli di esecuzione. Il thread principale non è un thread di dispatch (beh, a meno che tu non stia usando 'dispatch_main()', e in tal caso, non ha neanche uno runloop, ma probabilmente non lo stai facendo). Il runloop del thread principale si integra con GCD per drenare esplicitamente la coda principale come parte della normale operazione del runloop. 'dispatch_after' è parte di GCD (da qui la parola" dispatch "nel nome), non ha nulla a che fare con runloop. –