¿Cómo puedo obtener una hora precisa, por ejemplo, en milisegundos en Objective-C?

99

¿Existe una manera fácil de obtener una hora con mucha precisión?

Necesito calcular algunos retrasos entre las llamadas al método. Más específicamente, quiero calcular la velocidad de desplazamiento en un UIScrollView.

Gracias
fuente
aquí hay una pregunta muy relacionada que también puede ayudar a comprender las respuestas aquí ... ¡por favor, eche un vistazo!
Abbood

Respuestas:

127

NSDatey los timeIntervalSince*métodos devolverán un NSTimeIntervalque es un doble con una precisión de menos de milisegundos. NSTimeIntervalestá en segundos, pero usa el doble para darle mayor precisión.

Para calcular la precisión de tiempo de milisegundos, puede hacer:

// Get a current time for where you want to start measuring from
NSDate *date = [NSDate date];

// do work...

// Find elapsed time and convert to milliseconds
// Use (-) modifier to conversion since receiver is earlier than now
double timePassed_ms = [date timeIntervalSinceNow] * -1000.0;

Documentación en timeIntervalSinceNow .

Hay muchas otras formas de calcular este intervalo utilizando NSDate, y recomendaría mirar la documentación de la clase NSDateque se encuentra en NSDate Class Reference .

Jeff Thompson
fuente
3
En realidad, esto es lo suficientemente preciso para el caso de uso general.
logancautrell
4
¿Es seguro utilizar NSDates para calcular el tiempo transcurrido? Me parece que la hora del sistema puede avanzar o retroceder cuando se sincroniza con fuentes de hora externas, por lo que no podría confiar en el resultado. Realmente quieres un reloj que aumente monótonamente, ¿no es así?
Kristopher Johnson
1
Acabo de comparar NSDatey mach_absolute_time()en un nivel de alrededor de 30 ms. 27 vs. 29, 36 vs. 39, 43 vs. 45. NSDatefue más fácil de usar para mí y los resultados fueron lo suficientemente similares como para no importarme.
rey nevan
7
No es seguro usar NSDate para comparar el tiempo transcurrido, ya que el reloj del sistema puede cambiar en cualquier momento (debido a NTP, transiciones DST, segundos intercalares y muchas otras razones). Utilice mach_absolute_time en su lugar.
nevyn
@nevyn, acabas de guardar mi prueba de velocidad de Internet, ¡tío! Maldita sea, me alegro de haber leído los comentarios antes de copiar y pegar el código, je
Albert Renshaw
41

mach_absolute_time() se puede utilizar para obtener medidas precisas.

Ver http://developer.apple.com/qa/qa2004/qa1398.html

También está disponible CACurrentMediaTime(), que es esencialmente lo mismo pero con una interfaz más fácil de usar.

(Nota: esta respuesta se escribió en 2009. Consulte la respuesta de Pavel Alexeev para conocer las clock_gettime()interfaces POSIX más simples disponibles en las versiones más recientes de macOS e iOS).

Kristopher Johnson
fuente
1
En ese código hay una conversión extraña: la última línea del primer ejemplo es "return * (uint64_t *) & elapsedNano;" ¿por qué no simplemente "return (uint64_t) elapsedNano"?
Tyler
8
Core Animation (QuartzCore.framework) también proporciona un método conveniente CACurrentMediaTime(), que se convierte mach_absolute_time()directamente en un double.
otto
1
@Tyler, elapsedNanoes de tipo Nanoseconds, que no es un tipo entero simple. Es un alias de UnsignedWide, que es una estructura con dos campos enteros de 32 bits. Puede usar en UnsignedWideToUInt64()lugar del yeso si lo prefiere.
Ken Thomases
CoreServices es solo una biblioteca de Mac. ¿Existe un equivalente en iOS?
mm24
1
@ mm24 En iOS, use CACurrentMediaTime (), que está en Core Animation.
Kristopher Johnson
28

No utilice NSDate, CFAbsoluteTimeGetCurrentni gettimeofdaypara medir el tiempo transcurrido. Todo esto depende del reloj del sistema, que puede cambiar en cualquier momento debido a muchas razones diferentes, como la sincronización de la hora de la red (NTP), la actualización del reloj (sucede a menudo para ajustar la desviación), los ajustes de DST, los segundos intercalares, etc.

Esto significa que si está midiendo su velocidad de descarga o carga, obtendrá picos o caídas repentinas en sus números que no se correlacionan con lo que realmente sucedió; sus pruebas de rendimiento tendrán valores atípicos incorrectos extraños; y sus temporizadores manuales se activarán después de duraciones incorrectas. El tiempo puede incluso retroceder y terminar con deltas negativos, y puede terminar con una recursividad infinita o un código muerto (sí, he hecho ambos).

Utilice mach_absolute_time. Mide los segundos reales desde que se inició el kernel. Está aumentando monótonamente (nunca retrocederá) y no se ve afectado por los ajustes de fecha y hora. Dado que es difícil trabajar con él, aquí hay un envoltorio simple que le brinda NSTimeIntervals:

// LBClock.h
@interface LBClock : NSObject
+ (instancetype)sharedClock;
// since device boot or something. Monotonically increasing, unaffected by date and time settings
- (NSTimeInterval)absoluteTime;

- (NSTimeInterval)machAbsoluteToTimeInterval:(uint64_t)machAbsolute;
@end

// LBClock.m
#include <mach/mach.h>
#include <mach/mach_time.h>

@implementation LBClock
{
    mach_timebase_info_data_t _clock_timebase;
}

+ (instancetype)sharedClock
{
    static LBClock *g;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        g = [LBClock new];
    });
    return g;
}

- (id)init
{
    if(!(self = [super init]))
        return nil;
    mach_timebase_info(&_clock_timebase);
    return self;
}

- (NSTimeInterval)machAbsoluteToTimeInterval:(uint64_t)machAbsolute
{
    uint64_t nanos = (machAbsolute * _clock_timebase.numer) / _clock_timebase.denom;

    return nanos/1.0e9;
}

- (NSTimeInterval)absoluteTime
{
    uint64_t machtime = mach_absolute_time();
    return [self machAbsoluteToTimeInterval:machtime];
}
@end
nevyn
fuente
2
Gracias. ¿Qué pasa CACurrentMediaTime()con QuartzCore?
Cœur
1
Nota: también se puede usar en @import Darwin;lugar de#include <mach/mach_time.h>
Cœur
@ Cœur CACurrentMediaTime debería ser exactamente equivalente a mi código. ¡Buen descubrimiento! (El encabezado dice "Este es el resultado de llamar a mach_absolute_time () y convertir las unidades a segundos")
nevyn
"puede cambiar en cualquier momento debido a muchas razones diferentes, como la sincronización de la hora de la red (NTP), la actualización del reloj (sucede a menudo para ajustar la deriva), los ajustes de DST, los segundos intercalares, etc." - ¿Qué otras razones podría ser? Observo saltos hacia atrás de unos 50 ms sin conexión a Internet. Entonces, aparentemente, ninguna de estas razones debería aplicarse ...
Falko
@Falko no estoy seguro, pero si tuviera que adivinar, apostaría que el sistema operativo realiza la compensación de deriva por sí solo si nota que diferentes relojes de hardware no están sincronizados.
nevyn
14

CFAbsoluteTimeGetCurrent()devuelve el tiempo absoluto como doublevalor, pero no sé cuál es su precisión: puede que solo se actualice cada docena de milisegundos, o podría actualizar cada microsegundo, no lo sé.

Adam Rosenfield
fuente
2
Sin embargo, es un valor de punto flotante de doble precisión y proporciona una precisión de menos de milisegundos. Un valor de 72,89674947369 segundos no es infrecuente ...
Jim Dovey
25
@JimDovey: Tendría que decir que un valor de 72.89674947369segundos sería bastante poco común, considerando todos los demás valores que podría ser. ;)
FreeAsInBeer
3
@Jim: ¿Tiene alguna cita que indique que proporciona una precisión de menos de milisegundos (esta es una pregunta honesta)? Espero que todos aquí comprendan las diferencias entre exactitud y precisión .
Adam Rosenfield
5
CFAbsoluteTimeGetCurrent () llama a gettimeofday () en OS X y GetSystemTimeAsFileTime () en Windows. Aquí está el código fuente .
Jim Dovey
3
Ah, y gettimeofday () se implementa usando el temporizador de nanosegundos de Mach a través de mach_absolute_time () ; aquí está la fuente para la implementación de la página común de gettimeofday () en Darwin / ARM: opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
Jim Dovey
10

NO lo usaría mach_absolute_time()porque consulta una combinación del kernel y el procesador durante un tiempo absoluto usando ticks (probablemente un tiempo de actividad).

Qué usaría:

CFAbsoluteTimeGetCurrent();

Esta función está optimizada para corregir la diferencia en el software y hardware iOS y OSX.

Algo más geek

El cociente de una diferencia en mach_absolute_time()y AFAbsoluteTimeGetCurrent()siempre está alrededor de 24000011.154871

Aquí hay un registro de mi aplicación:

Por favor nota que el tiempo resultado final es una diferencia en CFAbsoluteTimeGetCurrent()'s

 2012-03-19 21:46:35.609 Rest Counter[3776:707] First Time: 353900795.609040
 2012-03-19 21:46:36.360 Rest Counter[3776:707] Second Time: 353900796.360177
 2012-03-19 21:46:36.361 Rest Counter[3776:707] Final Result Time (difference): 0.751137
 2012-03-19 21:46:36.363 Rest Counter[3776:707] Mach absolute time: 18027372
 2012-03-19 21:46:36.365 Rest Counter[3776:707] Mach absolute time/final time: 24000113.153295
 2012-03-19 21:46:36.367 Rest Counter[3776:707] -----------------------------------------------------
 2012-03-19 21:46:43.074 Rest Counter[3776:707] First Time: 353900803.074637
 2012-03-19 21:46:43.170 Rest Counter[3776:707] Second Time: 353900803.170256
 2012-03-19 21:46:43.172 Rest Counter[3776:707] Final Result Time (difference): 0.095619
 2012-03-19 21:46:43.173 Rest Counter[3776:707] Mach absolute time: 2294833
 2012-03-19 21:46:43.175 Rest Counter[3776:707] Mach absolute time/final time: 23999753.727777
 2012-03-19 21:46:43.177 Rest Counter[3776:707] -----------------------------------------------------
 2012-03-19 21:46:46.499 Rest Counter[3776:707] First Time: 353900806.499199
 2012-03-19 21:46:55.017 Rest Counter[3776:707] Second Time: 353900815.016985
 2012-03-19 21:46:55.018 Rest Counter[3776:707] Final Result Time (difference): 8.517786
 2012-03-19 21:46:55.020 Rest Counter[3776:707] Mach absolute time: 204426836
 2012-03-19 21:46:55.022 Rest Counter[3776:707] Mach absolute time/final time: 23999996.639500
 2012-03-19 21:46:55.024 Rest Counter[3776:707] -----------------------------------------------------
Nate Symer
fuente
Terminé usando mach_absolute_time()con a mach_timebase_info_datay luego lo hice (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));. Para obtener la base de tiempo de Mach, puede hacerlo(void)mach_timebase_info(&your_timebase);
Nate Symer
12
Según los documentos de CFAbsoluteTimeGetCurrent (), "La hora del sistema puede disminuir debido a la sincronización con referencias de tiempo externas o debido a un cambio explícito del reloj por parte del usuario". No entiendo por qué alguien querría usar algo como esto para medir el tiempo transcurrido, si es que puede retroceder.
Kristopher Johnson
6
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)

Uso:

CTTimeStart();
...
CTTimeEnd(@"that was a long time:");

Salida:

2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023
gngrwzrd
fuente
NSDatedepende del reloj del sistema, que puede modificarse en cualquier momento, lo que puede dar lugar a intervalos de tiempo incorrectos o incluso negativos. Úselo en su mach_absolute_timelugar para obtener el tiempo transcurrido correcto.
Cœur
mach_absolute_timepuede verse afectado por los reinicios del dispositivo. En su lugar, utilice la hora del servidor.
Kelin
5

Además, aquí se explica cómo calcular un 64 bits NSNumberinicializado con la época de Unix en milisegundos, en caso de que así sea como desee almacenarlo en CoreData. Necesitaba esto para mi aplicación que interactúa con un sistema que almacena fechas de esta manera.

  + (NSNumber*) longUnixEpoch {
      return [NSNumber numberWithLongLong:[[NSDate date] timeIntervalSince1970] * 1000];
  }
John Wright
fuente
3

Las funciones basadas en mach_absolute_timeson buenas para mediciones cortas.
Pero para mediciones largas, una advertencia importante es que dejan de hacer tictac mientras el dispositivo está inactivo.

Hay una función para obtener el tiempo desde el inicio. No se detiene mientras duerme. Además, gettimeofdayno es monótono, pero en mis experimentos siempre he visto que el tiempo de arranque cambia cuando cambia el tiempo del sistema, así que creo que debería funcionar bien.

func timeSinceBoot() -> TimeInterval
{
    var bootTime = timeval()
    var currentTime = timeval()
    var timeZone = timezone()

    let mib = UnsafeMutablePointer<Int32>.allocate(capacity: 2)
    mib[0] = CTL_KERN
    mib[1] = KERN_BOOTTIME
    var size = MemoryLayout.size(ofValue: bootTime)

    var timeSinceBoot = 0.0

    gettimeofday(&currentTime, &timeZone)

    if sysctl(mib, 2, &bootTime, &size, nil, 0) != -1 && bootTime.tv_sec != 0 {
        timeSinceBoot = Double(currentTime.tv_sec - bootTime.tv_sec)
        timeSinceBoot += Double(currentTime.tv_usec - bootTime.tv_usec) / 1000000.0
    }
    return timeSinceBoot
}

Y desde iOS 10 y macOS 10.12 podemos usar CLOCK_MONOTONIC:

if #available(OSX 10.12, *) {
    var uptime = timespec()
    if clock_gettime(CLOCK_MONOTONIC_RAW, &uptime) == 0 {
        return Double(uptime.tv_sec) + Double(uptime.tv_nsec) / 1000000000.0
    }
}

Para resumirlo:

  • Date.timeIntervalSinceReferenceDate - cambia cuando cambia la hora del sistema, no monótono
  • CFAbsoluteTimeGetCurrent() - no monótono, puede retroceder
  • CACurrentMediaTime() - deja de hacer tictac cuando el dispositivo está dormido
  • timeSinceBoot() - no duerme, pero puede que no sea monótono
  • CLOCK_MONOTONIC - no duerme, monótono, compatible desde iOS 10
Pavel Alexeev
fuente
1

Sé que este es antiguo, pero incluso yo me encontré vagando de nuevo, así que pensé en enviar mi propia opción aquí.

La mejor opción es consultar mi publicación de blog sobre esto: cronometraje en Objective-C: un cronómetro

Básicamente, escribí una clase que deja de mirar de una manera muy básica, pero está encapsulada, por lo que solo necesita hacer lo siguiente:

[MMStopwatchARC start:@"My Timer"];
// your work here ...
[MMStopwatchARC stop:@"My Timer"];

Y terminas con:

MyApp[4090:15203]  -> Stopwatch: [My Timer] runtime: [0.029]

en el registro ...

Una vez más, consulte mi publicación para obtener más información o descárguela aquí: MMStopwatch.zip

bladnman
fuente
No se recomienda su implementación. NSDatedepende del reloj del sistema, que puede modificarse en cualquier momento, lo que puede dar lugar a intervalos de tiempo incorrectos o incluso negativos. Úselo en su mach_absolute_timelugar para obtener el tiempo transcurrido correcto.
Cœur
1

Puede obtener la hora actual en milisegundos desde el 1 de enero de 1970 utilizando un NSDate:

- (double)currentTimeInMilliseconds {
    NSDate *date = [NSDate date];
    return [date timeIntervalSince1970]*1000;
}
Camilo Ortegón
fuente
Esto le da tiempo en milisegundos pero aún con precisión de segundos
dev
-1

Para aquellos, necesitamos la versión Swift de la respuesta de @Jeff Thompson:

// Get a current time for where you want to start measuring from
var date = NSDate()

// do work...

// Find elapsed time and convert to milliseconds
// Use (-) modifier to conversion since receiver is earlier than now
var timePassed_ms: Double = date.timeIntervalSinceNow * -1000.0

Espero que esto te ayude.

Victor Sigler
fuente
2
NSDatedepende del reloj del sistema, que puede modificarse en cualquier momento, lo que puede dar lugar a intervalos de tiempo incorrectos o incluso negativos. Úselo en su mach_absolute_timelugar para obtener el tiempo transcurrido correcto.
Cœur