2012-05-09 10 views
19

Mi chiedevo perché quando si crea un timer ripetuto in un blocco GCD, non funziona?Esegui ripetendo NSTimer con GCD?

Questo funziona bene:

-(void)viewDidLoad{ 
    [super viewDidLoad]; 
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES]; 
} 
-(void)runTimer{ 
    NSLog(@"hi"); 
} 

Ma questo lavoro doesent:

dispatch_queue_t myQueue; 

-(void)viewDidLoad{ 
    [super viewDidLoad]; 

    myQueue = dispatch_queue_create("someDescription", NULL); 
    dispatch_async(myQueue, ^{ 
     [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES]; 
    }); 
} 
-(void)runTimer{ 
    NSLog(@"hi"); 
} 

risposta

57

NSTimers sono in programma del run loop thread corrente. Tuttavia, i thread di invio GCD non hanno cicli di esecuzione, quindi programmare i timer in un blocco GCD non farà nulla.

Ci sono tre alternative ragionevoli:

  1. capire cosa corrono ciclo si desidera pianificare il timer, ed esplicitamente farlo. Utilizzare +[NSTimer timerWithTimeInterval:target:selector:userInfo:repeats:] per creare il timer e quindi -[NSRunLoop addTimer:forMode:] per programmarlo effettivamente sul ciclo di esecuzione che si desidera utilizzare. Ciò richiede di avere un handle sul ciclo di esecuzione in questione, ma puoi semplicemente usare +[NSRunLoop mainRunLoop] se vuoi farlo sul thread principale.
  2. Passare all'utilizzo di un timer dispatch source. Questo implementa un timer in un meccanismo che riconosce GCD, che eseguirà un blocco all'intervallo desiderato nella coda di tua scelta.
  3. dispatch_async() Esplicitamente alla coda principale prima di creare il timer. Questo è equivalente all'opzione # 1 che utilizza il ciclo di esecuzione principale (poiché creerà anche il timer sul thread principale).

Ovviamente, la vera domanda qui è, perché stai creando un timer da una coda GCD per cominciare?

+0

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. –

+0

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

+0

@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. –

-1

Questa è una cattiva idea . Stavo per cancellare questa risposta, ma l'ho lasciato qui per evitare che altri facessero lo stesso errore che ho fatto. Grazie a #Kevin_Ballard per aver indicato questo.

Faresti solo aggiungere una riga al vostro esempio e in questo modo funziona proprio come hai scritto:

[[NSRunLoop currentRunLoop] run]

modo si otterrebbe:

-(void)viewDidLoad{ 
     [super viewDidLoad]; 

     myQueue = dispatch_queue_create("someDescription", NULL); 
     dispatch_async(myQueue, ^{ 
      [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES]; 
      [[NSRunLoop currentRunLoop] run] 
     }); 
    } 

Dal momento che la coda myQueue contiene un NSThread e contiene una NSRunLoop e dal momento che il codice che viene eseguito dispatch_async il contesto di tale NSThread, currentRunLoop restituirebbe un ciclo corsa fermato associato al thread della coda.

+1

Questa è una cattiva idea. Ora hai cooptato un thread di invio da utilizzare come un runloop e non restituirai mai il controllo del thread su GCD. Questo è fondamentalmente come mettere un loop infinito in un blocco di dispatch. Prendere in consegna i thread di invio e non restituirli mai il controllo su GCD danneggerà le prestazioni dell'intero sistema, e se lo fai con un numero sufficiente di thread puoi effettivamente fare in modo che GCD interrompa completamente l'elaborazione dei blocchi inviati alle code non globali. –

+0

Il codice richiede solo un modo per uscire da runloop. Conservare un riferimento a NSTimer e invalidarlo nel momento appropriato. – seedante

0

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) 
}