2012-05-23 8 views
5

Sto cercando di creare un conto alla rovescia per il codice temporale SMPTE (HH: MM: SS: FF) su iOS. Fondamentalmente, è solo un timer per il conto alla rovescia con una risoluzione di 33.33333 ms. Non sono così sicuro che NSTimer sia abbastanza preciso da poter contare su eventi di fuoco per creare questo timer. Vorrei attivare un evento o chiamare un pezzo di codice ogni volta che questo timer aumenta/diminuisce.Come si crea un evento timer accurato in Objective-C/iOS?

Sono nuovo nell'Obiettivo-C quindi cerco la saggezza dalla comunità. Qualcuno ha suggerito la classe CADisplayLink, alla ricerca di alcuni consigli di esperti.

risposta

12

Prova CADisplayLink. Spara alla frequenza di aggiornamento (60 fps).

CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timerFired:)]; 
displayLink.frameInterval = 2; 
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 

Questo attiverà ogni 2 fotogrammi, ovvero 30 volte al secondo, il che sembra essere ciò che si sta cercando.

Si noti che questo è legato all'elaborazione del frame video, quindi è necessario eseguire il proprio lavoro nella richiamata molto rapidamente.

+0

Si noti che _frameInterval_ era obsoleto in iOS 10.0, utilizzare _preferredFramesPerSecond_, in questo modo: 'displayLink.preferredFramesPerSecond = 30.0;' per 30 fps – WongWray

1

Se si prendono di mira iOS 4+, è possibile utilizzare Grand Central Dispatch:

// Set the time, '33333333' nanoseconds in the future (33.333333ms) 
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 33333333); 
// Schedule our code to run 
dispatch_after(time, dispatch_get_main_queue(), ^{ 
    // your code to run here... 
}); 

Ciò richiederà che il codice dopo 33.333333ms. Se è questa intenzione di essere un affare ciclo sorta, si consiglia di utilizzare la funzione di dispatch_after_f invece che utilizza un puntatore a funzione, invece di un blocco:

void DoWork(void *context); 

void ScheduleWork() { 
    // Set the time, '33333333' nanoseconds in the future (33.333333ms) 
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 33333333); 
    // Schedule our 'DoWork' function to run 
    // Here I pass in NULL for the 'context', whatever you set that to will 
    // get passed to the DoWork function 
    dispatch_after_f(time, dispatch_get_main_queue(), NULL, &DoWork); 
} 

void DoWork(void *context) { 
    // ... 
    // Do your work here, updating an on screen counter or something 
    // ... 

    // Schedule our DoWork function again, maybe add an if statement 
    // so it eventually stops 
    ScheduleWork(); 
} 

E poi basta chiamare ScheduleWork(); quando si vuole avviare il timer. Per un ciclo ripetitivo, personalmente penso che questo sia un po 'più pulito rispetto al metodo di blocco sopra, ma per un compito una tantum preferisco decisamente il metodo a blocchi.

Vedere lo Grand Central Dispatch docs per ulteriori informazioni.

+1

Non penso che 'dispatch_after' sarà più preciso di' NSTimer'. In effetti, in iOS 4 sono abbastanza sicuro che quest'ultimo sia implementato in termini di primo. – benzado

+0

Penso che pianificare una nuova discussione ogni volta imponga alcune inesattezze, ma ammetto che non l'ho ancora testato. IOS potrebbe avere un overhead basso, vale la pena provare. –

4

Fondamentalmente non si hanno garanzie con NSTimer o dispatch_after; programmano il codice da attivare sul thread principale, ma se qualcos'altro richiede molto tempo per essere eseguito e blocca il thread principale, il timer non scatterà.

Detto questo, è possibile evitare facilmente il blocco del thread principale (utilizzare solo I/O asincrono) e le cose dovrebbero essere piuttosto buone.

Non si dice esattamente ciò che è necessario fare nel codice del timer, ma se tutto ciò che si deve fare è visualizzare un conto alla rovescia, si dovrebbe andare bene finchè si calcola il tempo SMPTE in base all'ora del sistema, e non il numero di secondi che secondo te dovrebbe essere trascorso in base all'intervallo del timer. Se lo fai, quasi sicuramente andrai alla deriva e non sarai più sincronizzato con il tempo reale. Invece, si noti il ​​tempo di inizio e poi fare tutti i calcoli in base a quanto segue:

// Setup 
timerStartDate = [[NSDate alloc] init]; 
[NSTimer scheduledTimer... 

- (void)timerDidFire:(NSTimer *)timer 
{ 
    NSTImeInterval elapsed = [timerStartDate timeIntervalSinceNow]; 
    NSString *smtpeCode = [self formatSMTPEFromMilliseconds:elapsed]; 
    self.label.text = smtpeCode; 
} 

Ora si visualizzerà il codice di tempo corretto non importa quante volte il timer viene licenziato. (Se il timer non si attiva abbastanza spesso, il timer non si aggiorna, ma quando si aggiorna sarà accurato. Non sarà mai fuori sincrono.)

Se si utilizza CADisplayLink, verrà chiamato il metodo veloce come il display si aggiorna. In altre parole, veloce come sarebbe utile, ma non più veloce. Se stai visualizzando il tempo, questa è probabilmente la strada da percorrere.

+0

Grazie! Poiché userò questo come base temporale per la decompressione dei fotogrammi video, non posso usare NSTimer per sincronizzare il display in modo affidabile. Più probabilmente CADisplayLink è la strada da percorrere, e la bloccherò a una velocità massima (33 ms) in modo che disegni fotogrammi non più lontani di 33ms ma meno se ci vuole troppo tempo per decomprimere. –

+0

Ho utilizzato questo approccio con dispatch_after e funziona perfettamente per i miei scopi. Grazie! –