Comment obtenir une heure précise, par exemple en millisecondes en Objective-C?

99

Existe-t-il un moyen facile d'obtenir une heure très précisément?

J'ai besoin de calculer certains délais entre les appels de méthode. Plus précisément, je souhaite calculer la vitesse de défilement dans un UIScrollView.

Merci
la source
voici une question très liée qui peut également aider à comprendre les réponses ici .. veuillez jeter un oeil!
abbood

Réponses:

127

NSDateet les timeIntervalSince*méthodes renverront un NSTimeIntervalqui est un double avec une précision inférieure à la milliseconde. NSTimeIntervalest en secondes, mais il utilise le double pour vous donner une plus grande précision.

Pour calculer la précision du temps en millisecondes, vous pouvez faire:

// 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;

Documentation sur timeIntervalSinceNow .

Il existe de nombreuses autres façons de calculer cet intervalle en utilisant NSDate, et je recommanderais NSDatede consulter la documentation de classe pour laquelle se trouve dans NSDate Class Reference .

Jeff Thompson
la source
3
En fait, c'est assez précis pour le cas d'utilisation général.
logancautrell
4
Est-il sûr d'utiliser NSDates pour calculer le temps écoulé? Il me semble que l'heure du système peut avancer ou reculer lors de la synchronisation avec des sources de temps externes, vous ne pourrez donc pas faire confiance au résultat. Vous voulez vraiment une horloge à augmentation monotone, n'est-ce pas?
Kristopher Johnson
1
Je viens de comparer NSDateet mach_absolute_time()au niveau d'environ 30ms. 27 vs 29, 36 vs 39, 43 vs 45. NSDateétait plus facile à utiliser pour moi et les résultats étaient assez similaires pour ne pas s'en soucier.
nevan king
7
Il n'est pas sûr d'utiliser NSDate pour comparer le temps écoulé, car l'horloge système peut changer à tout moment (en raison de NTP, des transitions DST, des secondes intercalaires et de nombreuses autres raisons). Utilisez plutôt mach_absolute_time.
nevyn
@nevyn vous venez d'enregistrer mon test de vitesse Internet, ty! Merde, je suis content d'avoir lu les commentaires avant de copier-coller le code heh
Albert Renshaw
41

mach_absolute_time() peut être utilisé pour obtenir des mesures précises.

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

Est également disponible CACurrentMediaTime(), qui est essentiellement la même chose mais avec une interface plus facile à utiliser.

(Remarque: cette réponse a été rédigée en 2009. Voir la réponse de Pavel Alexeev pour les clock_gettime()interfaces POSIX plus simples disponibles dans les nouvelles versions de macOS et iOS.)

Kristopher Johnson
la source
1
Dans ce code se trouve une conversion étrange - la dernière ligne du premier exemple est "return * (uint64_t *) & elapsedNano;" pourquoi pas simplement "return (uint64_t) elapsedNano"?
Tyler
8
Core Animation (QuartzCore.framework) fournit également une méthode pratique CACurrentMediaTime(), qui se convertit mach_absolute_time()directement en fichier double.
otto
1
@Tyler, elapsedNanoest de type Nanoseconds, qui n'est pas un type entier. C'est un alias de UnsignedWide, qui est une structure avec deux champs entiers 32 bits. Vous pouvez utiliser à la UnsignedWideToUInt64()place du casting si vous préférez.
Ken Thomases
CoreServices n'est qu'une bibliothèque Mac. Existe-t-il un équivalent dans iOS?
mm24
1
@ mm24 Sur iOS, utilisez CACurrentMediaTime (), qui se trouve dans Core Animation.
Kristopher Johnson
28

Veuillez ne pas utiliser NSDate, CFAbsoluteTimeGetCurrentou gettimeofdaypour mesurer le temps écoulé. Tout cela dépend de l'horloge système, qui peut changer à tout moment pour de nombreuses raisons différentes, telles que la synchronisation de l'heure du réseau (NTP), la mise à jour de l'horloge (il arrive souvent à s'ajuster à la dérive), les ajustements d'heure d'été, les secondes intercalaires, etc.

Cela signifie que si vous mesurez votre vitesse de téléchargement ou de téléchargement, vous obtiendrez des pics ou des baisses soudaines de vos chiffres qui ne correspondent pas à ce qui s'est réellement passé; vos tests de performance auront des valeurs aberrantes incorrectes; et vos minuteries manuelles se déclencheront après des durées incorrectes. Le temps peut même reculer, et vous vous retrouvez avec des deltas négatifs, et vous pouvez vous retrouver avec une récursion infinie ou un code mort (oui, j'ai fait les deux).

Utilisez mach_absolute_time. Il mesure de vraies secondes depuis le démarrage du noyau. Il augmente de manière monotone (ne recule jamais) et n'est pas affecté par les paramètres de date et d'heure. Comme il est difficile de travailler avec, voici un simple wrapper qui vous donne NSTimeInterval:

// 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
la source
2
Merci. Qu'en est-il CACurrentMediaTime()de QuartzCore?
Cœur
1
Note: on peut aussi utiliser à la @import Darwin;place de#include <mach/mach_time.h>
Cœur
@ Cœur CACurrentMediaTime devrait en fait être exactement équivalent à mon code. Bonne trouvaille! (L'en-tête dit "Ceci est le résultat de l'appel de mach_absolute_time () et de la conversion des unités en secondes")
nevyn
"peut changer à tout moment pour de nombreuses raisons différentes, telles que la synchronisation de l'heure du réseau (NTP), la mise à jour de l'horloge (il arrive souvent à s'ajuster à la dérive), les ajustements d'heure d'été, les secondes intercalaires, etc." - Quelles autres raisons cela pourrait-il être? J'observe des sauts en arrière d'environ 50 ms sans connexion Internet. Donc, apparemment, aucune de ces raisons ne devrait s'appliquer ...
Falko
@Falko pas sûr, mais si je devais deviner, je parierais que le système d'exploitation fait une compensation de dérive tout seul s'il remarque que différentes horloges matérielles sont désynchronisées?
nevyn
14

CFAbsoluteTimeGetCurrent()renvoie le temps absolu sous forme de doublevaleur, mais je ne sais pas quelle est sa précision - il peut ne se mettre à jour que toutes les douzaines de millisecondes, ou il peut se mettre à jour toutes les microsecondes, je ne sais pas.

Adam Rosenfield
la source
2
C'est une valeur à virgule flottante à double précision et fournit une précision inférieure à la milliseconde. Une valeur de 72,89674947369 secondes n'est pas rare ...
Jim Dovey
25
@JimDovey: Je dois dire qu'une valeur de 72.89674947369secondes serait assez rare, compte tenu de toutes les autres valeurs qu'elle pourrait être. ;)
FreeAsInBeer
3
@Jim: Avez-vous une citation qu'il fournit une précision inférieure à la milliseconde (c'est une question honnête)? J'espère que tout le monde ici comprend les différences entre l' exactitude et la précision .
Adam Rosenfield
5
CFAbsoluteTimeGetCurrent () appelle gettimeofday () sous OS X et GetSystemTimeAsFileTime () sous Windows. Voici le code source .
Jim Dovey
3
Oh, et gettimeofday () est implémenté en utilisant le minuteur de Mach nanosecondes via mach_absolute_time () ; voici la source de l'implémentation de la page commune de gettimeofday () sur Darwin / ARM: opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
Jim Dovey
10

Je n'utiliserais PAS mach_absolute_time()car il interroge une combinaison du noyau et du processeur pendant un temps absolu en utilisant des ticks (probablement un temps de disponibilité).

Ce que j'utiliserais:

CFAbsoluteTimeGetCurrent();

Cette fonction est optimisée pour corriger la différence entre le logiciel et le matériel iOS et OSX.

Quelque chose de geekier

Le quotient d'une différence entre mach_absolute_time()et AFAbsoluteTimeGetCurrent()est toujours autour de 24000011.154871

Voici un journal de mon application:

S'il vous plaît noter que le temps de résultat final est une différence dans 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
la source
J'ai fini par utiliser mach_absolute_time()avec un mach_timebase_info_dataet puis je l'ai fait (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));. Pour obtenir la base de temps mach, vous pouvez faire(void)mach_timebase_info(&your_timebase);
Nate Symer
12
Selon la documentation de CFAbsoluteTimeGetCurrent (), «l'heure système peut diminuer en raison de la synchronisation avec des références de temps externes ou en raison d'un changement d'utilisateur explicite de l'horloge». Je ne comprends pas pourquoi quiconque voudrait utiliser quelque chose comme ça pour mesurer le temps écoulé, si cela peut reculer.
Kristopher Johnson
6
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)

Usage:

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

Production:

2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023
gngrwzrd
la source
NSDatedépend de l'horloge du système qui peut être modifiée à tout moment, ce qui peut entraîner des intervalles de temps erronés ou même négatifs. Utilisez à la mach_absolute_timeplace pour obtenir le temps écoulé correct.
Cœur
mach_absolute_timepeut être affecté par les redémarrages de l'appareil. Utilisez plutôt l'heure du serveur.
kelin
5

Aussi, voici comment calculer un 64 bits NSNumberinitialisé avec l'époque Unix en millisecondes, au cas où vous voudriez le stocker dans CoreData. J'en avais besoin pour mon application qui interagit avec un système qui stocke les dates de cette façon.

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

Les fonctions basées sur mach_absolute_timesont bonnes pour les mesures courtes.
Mais pour les longues mesures, une mise en garde importante est qu'ils cessent de tourner pendant que l'appareil est en veille.

Il existe une fonction pour obtenir le temps depuis le démarrage. Cela ne s'arrête pas pendant le sommeil. De plus, ce gettimeofdayn'est pas monotone, mais dans mes expériences, j'ai toujours vu que l'heure de démarrage change lorsque l'heure du système change, donc je pense que cela devrait fonctionner correctement.

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
}

Et depuis iOS 10 et macOS 10.12, nous pouvons utiliser 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
    }
}

Résumer:

  • Date.timeIntervalSinceReferenceDate - change lorsque l'heure du système change, pas monotone
  • CFAbsoluteTimeGetCurrent() - pas monotone, peut revenir en arrière
  • CACurrentMediaTime() - arrête de tic-tac lorsque l'appareil est en veille
  • timeSinceBoot() - ne dort pas, mais n'est peut-être pas monotone
  • CLOCK_MONOTONIC - ne dort pas, monotone, pris en charge depuis iOS 10
Pavel Alexeev
la source
1

Je sais que c'est un ancien, mais même moi, je me suis retrouvé à errer à nouveau, alors j'ai pensé que je soumettrais ma propre option ici.

Le mieux est de consulter mon article de blog à ce sujet: Chronométrage en Objective-C: Un chronomètre

Fondamentalement, j'ai écrit une classe qui arrête de regarder de manière très basique mais qui est encapsulée afin que vous n'ayez qu'à faire ce qui suit:

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

Et vous vous retrouvez avec:

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

dans le journal ...

Encore une fois, consultez mon article pour un peu plus ou téléchargez-le ici: MMStopwatch.zip

bladnman
la source
Votre implémentation n'est pas recommandée. NSDatedépend de l'horloge du système qui peut être modifiée à tout moment, ce qui peut entraîner des intervalles de temps erronés ou même négatifs. Utilisez à la mach_absolute_timeplace pour obtenir le temps écoulé correct.
Cœur
1

Vous pouvez obtenir l'heure actuelle en millisecondes depuis le 1er janvier 1970 en utilisant un NSDate:

- (double)currentTimeInMilliseconds {
    NSDate *date = [NSDate date];
    return [date timeIntervalSince1970]*1000;
}
Camilo Ortegón
la source
Cela vous donne le temps en millisecondes, mais il est toujours précis en secondes
dev
-1

Pour ceux-ci, nous avons besoin de la version Swift de la réponse 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

J'espère que cela vous aidera.

Victor Sigler
la source
2
NSDatedépend de l'horloge du système qui peut être modifiée à tout moment, ce qui peut entraîner des intervalles de temps erronés ou même négatifs. Utilisez à la mach_absolute_timeplace pour obtenir le temps écoulé correct.
Cœur