In Objective-C verstrichene Zeit

153

Ich muss die zwischen zwei Ereignissen verstrichene Zeit ermitteln, z. B. das Erscheinen eines UIView und die erste Reaktion des Benutzers.

Wie kann ich es in Objective-C erreichen?

Ilya Suzdalnitski
quelle

Antworten:

267
NSDate *start = [NSDate date];
// do stuff...
NSTimeInterval timeInterval = [start timeIntervalSinceNow];

timeInterval ist der Unterschied zwischen Start und Jetzt in Sekunden mit einer Genauigkeit von weniger als einer Millisekunde.

Kann Berk Güder
quelle
18
@NicolasZozol können Sie verwenden fabs(...), um den absoluten Wert eines Floats zu erhalten. Z.B. NSTimeInterval timeInterval = fabs([start timeIntervalSinceNow]);
Also
1
Der Wert von timeInterval scheint negativ zu sein.
Jacky
Wenn Sie einen negativen Wert erhalten - multiplizieren Sie mit -1 und Sie sind gut
PinkFloydRocks
10
Wenn Sie sich darauf verlassen, [NSDate date]kann dies zu schwer zu verfolgenden Fehlern führen. Weitere Informationen finden Sie in dieser Antwort .
Sinnvoll
@PinkFloydRocks - Was? Wie würde ein negativer Wert jemals legitim auftreten?
Todd Lehman
228

Sie sollten sich nicht auf [NSDate date]Timing-Zwecke verlassen, da dies die verstrichene Zeit über- oder unterschätzen kann. Es gibt sogar Fälle, in denen Ihr Computer scheinbar eine Zeitreise unternimmt, da die verstrichene Zeit negativ ist! (ZB wenn sich die Uhr während des Timings rückwärts bewegte.)

Laut Aria Haghighi in der Vorlesung "Advanced iOS Gesture Recognition" des Stanford iOS-Kurses Winter 2013 (34:00) sollten Sie diese verwenden, CACurrentMediaTime()wenn Sie ein genaues Zeitintervall benötigen.

Ziel c:

#import <QuartzCore/QuartzCore.h>
CFTimeInterval startTime = CACurrentMediaTime();
// perform some action
CFTimeInterval elapsedTime = CACurrentMediaTime() - startTime;

Schnell:

let startTime = CACurrentMediaTime()
// perform some action
let elapsedTime = CACurrentMediaTime() - startTime

Der Grund dafür ist, dass die [NSDate date]Synchronisierung auf dem Server zu "Zeitsynchronisierungsproblemen" führen kann, die zu sehr schwer zu verfolgenden Fehlern führen können. CACurrentMediaTime()Andererseits ist dies eine Gerätezeit, die sich bei diesen Netzwerksynchronisierungen nicht ändert.

Sie müssen das QuartzCore-Framework zu den Einstellungen Ihres Ziels hinzufügen .

Sinnvoll
quelle
17
Bitte stimmen Sie dem zu. Obwohl ich denke, dass jeder zustimmen kann, dass NSDate Ihre erste Option sein sollte, hat dieser Vorschlag ein Problem von mir gelöst. Beim Aufruf [NSDate date]innerhalb eines Abschlussblocks innerhalb eines Versandblocks wurde dieselbe "aktuelle" Zeit angezeigt, die [NSDate date]unmittelbar vor der Ausführung gemeldet wurde, als der Punkt erreicht wurde, an dem meine Blöcke erstellt wurden. CACurrentMediaTime()Dieses Problem wurde behoben.
n00neimp0rtant
Wie würden wir die elapsedTime für die Anzeige wie mm: ss verwenden?
CyberMew
@CyberMew: Das ist ein separates Problem. Siehe Wie zerlege ich ein NSTimeInterval auf dem iPhone in Jahr, Monate, Tage, Stunden, Minuten und Sekunden? für einige Lösungen.
Sinnvoller
12
Beachten Sie, dass das CACurrentMediaTime()Ticken aufhört, wenn das Gerät in den Ruhezustand wechselt. Wenn Sie testen, während das Gerät von einem Computer getrennt ist, sperren Sie es und warten Sie ca. 10 Minuten. Sie werden feststellen, dass CACurrentMediaTime()dies nicht mit der Wanduhrzeit Schritt hält.
russbishop
Hinzufügen und Importieren von #import <QuartzCore / QuartzCore.h>
steveen zoleko
23

Verwenden Sie die timeIntervalSinceDateMethode

NSTimeInterval secondsElapsed = [secondDate timeIntervalSinceDate:firstDate];

NSTimeIntervalist nur ein double, definieren Sie in NSDatewie folgt :

typedef double NSTimeInterval;
Marco Lazzeri
quelle
15

Für alle, die hierher kommen und nach einer getTickCount () - Implementierung für iOS suchen, ist hier meine, nachdem sie verschiedene Quellen zusammengestellt haben.

Zuvor hatte ich einen Fehler in diesem Code (ich habe zuerst durch 1000000 geteilt), der eine Quantisierung der Ausgabe auf meinem iPhone 6 verursachte (möglicherweise war dies kein Problem auf dem iPhone 4 / etc oder ich habe es einfach nie bemerkt). Beachten Sie, dass, wenn diese Division nicht zuerst ausgeführt wird, ein gewisses Überlaufrisiko besteht, wenn der Zähler der Zeitbasis ziemlich groß ist. Wenn jemand neugierig ist, gibt es hier einen Link mit viel mehr Informationen: https://stackoverflow.com/a/23378064/588476

Angesichts dieser Informationen ist es möglicherweise sicherer, die Apple-Funktion zu verwenden CACurrentMediaTime !

Ich habe den mach_timebase_infoAnruf auch einem Benchmarking unterzogen und auf meinem iPhone 6 dauert es ungefähr 19 ns. Daher habe ich den (nicht threadsicheren) Code entfernt, der die Ausgabe dieses Anrufs zwischengespeichert hat.

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

uint64_t getTickCount(void)
{
    mach_timebase_info_data_t sTimebaseInfo;
    uint64_t machTime = mach_absolute_time();

    // Convert to milliseconds
    mach_timebase_info(&sTimebaseInfo);
    machTime *= sTimebaseInfo.numer;
    machTime /= sTimebaseInfo.denom;
    machTime /= 1000000; // convert from nanoseconds to milliseconds

    return machTime;
}

Beachten Sie das potenzielle Risiko eines Überlaufs in Abhängigkeit von der Ausgabe des Zeitbasisaufrufs. Ich vermute (aber weiß nicht), dass es eine Konstante für jedes iPhone-Modell sein könnte. auf meinem iPhone 6 war es125/3 .

Die Lösung CACurrentMediaTime()ist ziemlich trivial:

uint64_t getTickCount(void)
{
    double ret = CACurrentMediaTime();
    return ret * 1000;
}
Wayne Uroda
quelle
Weiß jemand, warum der Aufruf mach_timebase_info redundant (nichtig) umgewandelt wird?
Simon Tillson
@SimonTillson, das ist eine gute Frage - entweder musste eine Warnung zum Schweigen gebracht werden, oder ich habe sie von einem anderen Ort kopiert und einfach nicht bemerkt, dass sie gelöscht werden soll. Ich kann mich nicht erinnern.
Wayne Uroda
2
Dies ist nicht threadsicher. mach_timebase_info()setzt die Übergabe auf zurück mach_timebase_info_data_t, {0,0}bevor sie auf die tatsächlichen Werte gesetzt wird. Dies kann zu einer Division durch Null führen, wenn Code von mehreren Threads aufgerufen wird. Verwenden Sie dispatch_once()stattdessen (oder CACurrentMediaTimedies ist nur die in Sekunden umgerechnete Mach-Zeit).
Wunder. Mäuse
Danke @ Wonder.mice, ich habe meine Antwort aktualisiert (habe auch ein Problem mit der Quantisierung gefunden, ich beschuldige mich, 6 Jahre jünger zu sein ...)
Wayne Uroda
4

Verwenden Sie die timeIntervalSince1970-Funktion der NSDate-Klasse wie folgt:

double start = [startDate timeIntervalSince1970];
double end = [endDate timeIntervalSince1970];
double difference = end - start;

Im Grunde ist es das, was ich benutze, um den Unterschied in Sekunden zwischen 2 verschiedenen Daten zu vergleichen. Überprüfen Sie auch diesen Link hier

Raj
quelle
0

Die anderen Antworten sind richtig (mit einer Einschränkung *). Ich füge diese Antwort einfach hinzu, um eine Beispielverwendung zu zeigen:

- (void)getYourAffairsInOrder
{
    NSDate* methodStart = [NSDate date];  // Capture start time.

    // … Do some work …

    NSLog(@"DEBUG Method %s ran. Elapsed: %f seconds.", __func__, -([methodStart timeIntervalSinceNow]));  // Calculate and report elapsed time.
}

Auf der Debugger-Konsole sehen Sie ungefähr Folgendes:

DEBUG Method '-[XMAppDelegate getYourAffairsInOrder]' ran. Elapsed: 0.033827 seconds.

* Vorsichtsmaßnahme: Verwenden Sie NSDate, wie bereits erwähnt, die Berechnung der verstrichenen Zeit nur für gelegentliche Zwecke. Ein solcher Zweck könnte das allgemeine Testen sein, das grobe Profiling, bei dem Sie nur eine ungefähre Vorstellung davon haben möchten, wie lange eine Methode dauert.

Das Risiko besteht darin, dass sich die aktuelle Zeiteinstellung des Geräts aufgrund der Synchronisierung der Netzwerkuhr jederzeit ändern kann. Die NSDateZeit könnte also jederzeit vorwärts oder rückwärts springen.

Basil Bourque
quelle