Upłynął czas w Objective-C


153

Potrzebuję czasu, jaki upłynął między dwoma zdarzeniami, na przykład pojawieniem się UIView i pierwszą reakcją użytkownika.

Jak mogę to osiągnąć w Objective-C?

Odpowiedzi:


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

timeInterval to różnica między startem a teraz, w sekundach, z dokładnością poniżej milisekundy.


18
@NicolasZozol możesz użyć, fabs(...)aby uzyskać wartość bezwzględną pływaka. Na przykład. NSTimeInterval timeInterval = fabs([start timeIntervalSinceNow]);
So Over It,

1
wydaje się, że wartość timeInterval jest ujemna.
Jacky

jeśli otrzymasz wartość ujemną - pomnóż przez -1 i jesteś dobry
PinkFloydRocks

10
Poleganie na [NSDate date]może prowadzić do trudnych do śledzenia błędów, zobacz tę odpowiedź, aby uzyskać więcej informacji.
Senseful

@PinkFloydRocks - co? Jak mogłaby kiedykolwiek wystąpić negatywna wartość?
Todd Lehman,

228

Nie należy polegać na [NSDate date]czasie, ponieważ może on zawyżać lub zaniżać czas, który upłynął. Istnieją nawet przypadki, w których twój komputer pozornie podróżuje w czasie, ponieważ upływający czas będzie ujemny! (Np. Jeśli zegar przesunął się do tyłu podczas pomiaru czasu).

Według Arii Haghighi w wykładzie „Advanced iOS Gesture Recognition” na kursie Winter 2013 Stanford iOS (34:00), CACurrentMediaTime()jeśli potrzebujesz dokładnego interwału czasowego , powinieneś używać .

Cel C:

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

Szybki:

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

Powodem jest to, że [NSDate date]synchronizuje się na serwerze, więc może to prowadzić do „problemów z synchronizacją czasu”, co może prowadzić do bardzo trudnych do śledzenia błędów. CACurrentMediaTime()z drugiej strony jest to czas urządzenia, który nie zmienia się podczas tych synchronizacji sieciowych.

Będziesz musiał dodać strukturę QuartzCore do ustawień celu.


17
Proszę, zagłosuj na to. Chociaż myślę, że każdy może się zgodzić, że NSDate powinno być pierwszą opcją, ta sugestia rozwiązała mój problem. Podczas wywoływania [NSDate date]w bloku uzupełniania w bloku wysyłania otrzymywałem ten sam „bieżący” czas, który był zgłaszany [NSDate date]bezpośrednio przed wykonaniem punktu, w którym zostały utworzone moje bloki. CACurrentMediaTime()rozwiązał ten problem.
n00neimp0rtant

Jak wykorzystalibyśmy czas elapsedTime do wyświetlenia takiego jak mm: ss?
CyberMew

@CyberMew: To osobny problem. Zobacz, jak rozbić NSTimeInterval na rok, miesiące, dni, godziny, minuty i sekundy na iPhonie? dla niektórych rozwiązań.
Senseful,

12
Pamiętaj, że CACurrentMediaTime()przestaje tykać, gdy urządzenie przechodzi w stan uśpienia. Jeśli testujesz urządzenie odłączone od komputera, zablokuj je, a następnie odczekaj ~ 10 minut, a okaże się, że CACurrentMediaTime()nie nadąża za zegarem ściennym.
Russbishop

dodaj i zaimportuj #import <QuartzCore / QuartzCore.h>
steveen zoleko

23

Użyj timeIntervalSinceDatemetody

NSTimeInterval secondsElapsed = [secondDate timeIntervalSinceDate:firstDate];

NSTimeIntervalto tylko a double, zdefiniuj w NSDateten sposób:

typedef double NSTimeInterval;

15

Dla każdego, kto szuka implementacji getTickCount () dla iOS, oto moje po połączeniu różnych źródeł.

Wcześniej miałem błąd w tym kodzie (najpierw podzieliłem przez 1000000), który powodował kwantyzację danych wyjściowych na moim iPhonie 6 (być może nie był to problem na iPhonie 4 / itp. Lub po prostu nigdy tego nie zauważyłem). Zwróć uwagę, że nie wykonując najpierw tego podziału, istnieje pewne ryzyko przepełnienia, jeśli licznik podstawy czasu jest dość duży. Jeśli ktoś jest ciekawy, tutaj znajduje się link z wieloma dodatkowymi informacjami: https://stackoverflow.com/a/23378064/588476

W świetle tych informacji może bezpieczniej jest korzystać z funkcji Apple CACurrentMediaTime!

mach_timebase_infoPrzeprowadziłem również test porównawczy połączenia i na moim iPhonie 6 zajmuje to około 19ns, więc usunąłem (nie zabezpieczony wątkami) kod, który buforował dane wyjściowe tego połączenia.

#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;
}

Należy być świadomym potencjalnego ryzyka przepełnienia w zależności od wyniku wywołania podstawy czasu. Podejrzewam (ale nie wiem), że może to być stała dla każdego modelu iPhone'a. na moim iPhonie 6 to było 125/3.

Zastosowanie rozwiązania CACurrentMediaTime()jest dość trywialne:

uint64_t getTickCount(void)
{
    double ret = CACurrentMediaTime();
    return ret * 1000;
}

Czy ktoś wie, dlaczego w wywołaniu mach_timebase_info występuje nadmiarowe (void) rzutowanie?
Simon Tillson,

@SimonTillson to dobre pytanie - albo trzeba było wyciszyć ostrzeżenie, albo skopiowałem je z innego miejsca i po prostu nie zauważyłem, żebym je usunął. Nie pamiętam.
Wayne Uroda

2
To nie jest bezpieczne dla wątków. mach_timebase_info()zresetuje przekazywane w mach_timebase_info_data_tcelu {0,0}przed ustawieniem go do rzeczywistych wartości, które mogą spowodować dzielenie przez zero, jeśli kod jest wywoływana z wielu wątków. Użyj dispatch_once()zamiast tego (lub CACurrentMediaTimektóry jest po prostu czasem mach przekonwertowanym na sekundy).
Wonder.mice

Dzięki @ Wonder.mice, zaktualizowałem odpowiedź (znalazłem problem z kwantyzacją, winię za mnie 6-latka młodszego ...)
Wayne Uroda


4

użyj funkcji timeIntervalSince1970 klasy NSDate jak poniżej:

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

w zasadzie to właśnie używam do porównania różnicy w sekundach między 2 różnymi datami. sprawdź również ten link tutaj


0

Pozostałe odpowiedzi są poprawne (z zastrzeżeniem *). Dodaję tę odpowiedź, aby pokazać przykładowe użycie:

- (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.
}

W konsoli debugera zobaczysz coś takiego:

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

* Uwaga: jak wspominali inni, używaj NSDatedo obliczania czasu, który upłynął, tylko do celów dorywczych. Jednym z takich celów może być powszechne testowanie, prymitywne profilowanie, w którym chcesz po prostu uzyskać ogólne pojęcie o tym, jak długo trwa metoda.

Istnieje ryzyko, że bieżące ustawienie zegara urządzenia może się zmienić w dowolnym momencie z powodu synchronizacji zegara sieciowego. NSDateCzas mógł więc w każdej chwili przeskoczyć do przodu lub do tyłu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.