2012-05-23 14 views
5

Estoy buscando crear un temporizador de cuenta regresiva para SMPTE Timecode (HH: MM: SS: FF) en iOS. Básicamente, es solo un temporizador de cuenta regresiva con una resolución de 33.33333ms. No estoy tan seguro de que NSTimer sea lo suficientemente preciso como para contar con eventos de fuego para crear este temporizador. Me gustaría disparar un evento o llamar a un fragmento de código cada vez que este temporizador aumenta o disminuye.¿Cómo creo un evento de temporizador preciso en Objective-C/iOS?

Soy nuevo en Objective-C, así que estoy buscando la sabiduría de la comunidad. Alguien ha sugerido la clase CADisplayLink, en busca de algún consejo experto.

Respuesta

12

Pruebe CADisplayLink. Dispara a la frecuencia de actualización (60 fps).

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

Esto disparará cada 2 cuadros, que es 30 veces por segundo, que parece ser lo que está buscando.

Tenga en cuenta que esto está vinculado al procesamiento de cuadros de video, por lo que debe hacer su trabajo en la devolución de llamada muy rápidamente.

+0

Tenga en cuenta que _frameInterval_ está en desuso en iOS 10.0, use _preferredFramesPerSecond_, así: 'displayLink.preferredFramesPerSecond = 30.0;' para 30fps – WongWray

1

Si el idioma de iOS 4, puede utilizar 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... 
}); 

Esto va a llamar a ese código después 33.333333ms. Si es este va a ser un reparto sorta bucle, es posible que desee utilizar la función dispatch_after_f vez que utiliza un puntero de función en lugar de un bloque:

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

Y luego simplemente llamar ScheduleWork(); cuando se quiere iniciar el temporizador. Para un bucle de repetición, personalmente creo que esto es un poco más limpio que el método de bloqueo anterior, pero para una tarea de una sola vez definitivamente prefiero el método de bloque.

Consulte el Grand Central Dispatch docs para obtener más información.

+1

No creo que 'dispatch_after' va a ser más preciso que' NSTimer'. De hecho, en iOS 4 estoy bastante seguro de que este último se implementa en términos de lo primero. – benzado

+0

Creo que programar un nuevo hilo cada vez impondría algunas imprecisiones, pero admitiré que no lo he probado. IOS podría tener algunos gastos generales bajos, vale la pena intentarlo. –

4

Básicamente no tiene garantías con NSTimer o dispatch_after; programan el código para que se active en el hilo principal, pero si algo más tarda en ejecutarse y bloquea el hilo principal, el temporizador no se disparará.

Dicho esto, puede evitar fácilmente el bloqueo del hilo principal (use solo E/S asincrónicas) y las cosas deberían ser bastante buenas.

No dice exactamente lo que necesita hacer en el código del temporizador, pero si todo lo que tiene que hacer es mostrar una cuenta atrás, debe estar bien siempre que calcule el tiempo SMPTE basado en la hora del sistema, y no la cantidad de segundos que cree que debería haber transcurrido según el intervalo de su temporizador. Si haces eso, es casi seguro que te desvíes y te desincronices con el tiempo real. En su lugar, indicar la hora de inicio y luego hacer todas las matemáticas basado en que:

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

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

Ahora se mostrará el código de tiempo correcto no importa cuántas veces se dispara el temporizador. (Si el temporizador no se dispara con la frecuencia suficiente, el temporizador no se actualizará, pero cuando se actualice será preciso. Nunca se desincronizará).

Si utiliza CADisplayLink, su método se llamará tan rápido como se actualice la pantalla. En otras palabras, tan rápido como sería útil, pero no más rápido. Si está mostrando la hora, ese es probablemente el camino a seguir.

+0

Gracias! Como usaré esto como la base de tiempo para descomprimir marcos de video, no puedo usar NSTimer para sincronizar de manera confiable la pantalla. Lo más probable es que CADisplayLink sea el camino a seguir, y lo bloquearé a una velocidad máxima (33 ms) para que dibuje fotogramas a no más de 33 ms de separación, pero menos si se tarda demasiado tiempo en descomprimir. –

+0

Utilicé este enfoque con dispatch_after, y funciona muy bien para mis propósitos. ¡Gracias! –

Cuestiones relacionadas