Як я можу отримати точний час, наприклад, в мілісекундах в Objective-C?


99

Чи є простий спосіб дуже точно отримати час?

Мені потрібно обчислити деякі затримки між викликами методів. Більш конкретно, я хочу обчислити швидкість прокрутки в UIScrollView.


ось дуже пов'язане питання, яке також може допомогти зрозуміти відповіді тут .. будь ласка, подивіться!
abbood


Відповіді:


127

NSDateі timeIntervalSince*методи повернуть a, NSTimeIntervalщо є подвійним з точністю до мілісекунди. NSTimeIntervalзнаходиться в секундах, але він використовує подвійний, щоб забезпечити вам більшу точність.

Для того, щоб обчислити точність мілісекундного часу, ви можете зробити:

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

Документація про часIntervalSinceNow .

Існує багато інших способів розрахунку цього інтервалу за допомогою NSDate, і я б рекомендував переглянути документацію класу, NSDateяку ви знайдете в NSDate Class Reference .


3
Насправді це досить точно для загального випадку використання.
logancautrell

4
Чи безпечно використовувати NSDates для обчислення минулого часу? Мені здається, що системний час може йти вперед або назад під час синхронізації із зовнішніми джерелами часу, тому ви не зможете довіряти результату. Ви дійсно хочете монотонно збільшувати годинник, чи не так?
Крістофер Джонсон

1
Я тільки порівняв NSDateі mach_absolute_time()приблизно на рівні 30 мс. 27 проти 29, 36 проти 39, 43 проти 45. NSDateмені було легше використовувати, і результати були досить схожі, щоб не хвилювати.
король неван

7
Використовувати NSDate не безпечно для порівняння минулого часу, оскільки системний годинник може змінюватися в будь-який час (через NTP, переходи DST, стрибкові секунди та багато інших причин). Використовуйте замість mach_absolute_time.
невин

@nevyn, ти щойно врятував мій тест на швидкість інтернету, ти! Чорт, я радий, що я прочитав коментарі перед тим, як скопіювати вставлення коду хе
Альберт Реншо

41

mach_absolute_time() може бути використаний для отримання точних вимірювань.

Дивіться http://developer.apple.com/qa/qa2004/qa1398.html

Також є в наявності CACurrentMediaTime(), що по суті те саме, але з більш простим у використанні інтерфейсом.

(Примітка. Ця відповідь була написана у 2009 році. Див. Відповідь Павла Алексєєва щодо простіших clock_gettime()інтерфейсів POSIX, доступних у новіших версіях macOS та iOS.)


1
У цьому коді дивне перетворення - останній рядок першого прикладу - "return * (uint64_t *) & elapsedNano;" чому б не просто "return (uint64_t) elapsedNano"?
Тайлер

8
Основна анімація (QuartzCore.framework) також забезпечує зручний метод CACurrentMediaTime(), який перетворюється mach_absolute_time()безпосередньо в a double.
otto

1
@Tyler elapsedNanoмає тип Nanoseconds, який не є простим цілим числом. Це псевдонім UnsignedWide, який представляє собою структуру з двома 32-бітовими цілими полями. UnsignedWideToUInt64()Якщо ви хочете, ви можете використовувати замість акторів.
Ken Thomases

CoreServices - це лише бібліотека Mac. Чи є еквівалент в iOS?
mm24

1
@ mm24 На iOS використовуйте CACurrentMediaTime (), який знаходиться в Core Animation.
Крістофер Джонсон

28

Будь ласка , не використовуйте NSDate, CFAbsoluteTimeGetCurrentабо gettimeofdayдля вимірювання витраченого часу. Все це залежить від системного годинника, який може змінюватися в будь-який час через безліч різних причин, таких як мережева синхронізація часу (NTP), оновлення годин (часто трапляється для налаштування дрейфу), налаштування DST, стрибкові секунди тощо.

Це означає, що якщо ви вимірюєте швидкість завантаження або завантаження, у вас з’являться раптові сплески або падіння ваших цифр, які не співвідносяться з тим, що відбулося насправді; ваші тести на ефективність матимуть дивні невірні виграші; і ваші ручні таймери спрацьовуватимуть після неправильної тривалості. Час може навіть повернутися назад, і ви закінчитеся з негативними дельтами, і ви можете закінчитися нескінченною рекурсією або мертвим кодом (так, я зробив це обоє).

Використовуйте mach_absolute_time. Він вимірює реальні секунди з моменту завантаження ядра. Він монотонно збільшується (ніколи не піде назад) і не впливає на налаштування дати та часу. Оскільки з цим боляче працювати, ось простий пакунок, який дає вам 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

2
Дякую. Що CACurrentMediaTime()з QuartzCore?
Cœur

1
Примітка: @import Darwin;замість цього можна використовувати#include <mach/mach_time.h>
Cœur

@ Cœur CACurrentMediaTime насправді повинен бути рівнозначним моєму коду. Гарна знахідка! (Заголовок каже: "Це результат виклику mach_absolute_time () та перетворення одиниць у секунди")
nevyn

"може змінюватися в будь-який час через багато різних причин, таких як мережева синхронізація часу (NTP), оновлення годин (часто трапляється для налаштування дрейфу), налаштування DST, стрибкові секунди тощо". - Які ще причини це могли бути? Я спостерігаю стрибки відстані близько 50 мс, не маючи підключення до Інтернету. Тож, мабуть, жодна з цих причин не повинна застосовуватися ...
Фалько

@Falko не впевнений, але якщо мені доведеться здогадуватися, я б став до того, що ОС робить самостійно компенсацію, якщо вона помічає, що різні годинники обладнання не синхронізовані?
невин

14

CFAbsoluteTimeGetCurrent()повертає абсолютний час як doubleзначення, але я не знаю, у чому його точність - він може оновлюватись лише кожні десяток мілісекунд, або він може оновлювати кожну мікросекунду, я не знаю.


2
Однак це значення з плаваючою комою з подвоєною точністю і забезпечує точність субмілісекунд. Значення 72,89674947369 секунд - не рідкість ...
Джим Дові

25
@JimDovey: Я б сказав, що значення 72.89674947369секунд було б досить рідкісним, враховуючи всі інші значення, якими воно могло бути. ;)
FreeAsInBeer

3
@Jim: Чи є у вас цитування, що воно забезпечує субмілісекундну точність (це чесне запитання)? Я сподіваюся, що всі тут розуміють відмінності між точністю та точністю .
Адам Розенфілд

5
CFAbsoluteTimeGetCurrent () викликає gettimeofday () на OS X та GetSystemTimeAsFileTime () в Windows. Ось вихідний код .
Джим Дові

3
О, і gettimeofday () реалізується за допомогою таймера наносекунду Mach через mach_absolute_time () ; ось джерело для загальної сторінки впровадження gettimeofday () у Дарвіні / ARM: opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
Джим Дові

10

Я б НЕ використовував, mach_absolute_time()тому що він запитує комбінацію ядра та процесора протягом абсолютного часу, використовуючи кліщі (ймовірно, тривалість роботи).

Що б я використав:

CFAbsoluteTimeGetCurrent();

Ця функція оптимізована для виправлення різниці програмного та апаратного забезпечення iOS та OSX.

Щось Geekier

Коефіцієнт різниці в mach_absolute_time()і AFAbsoluteTimeGetCurrent()завжди становить близько 24000011.154871

Ось журнал мого додатка:

Зверніть увагу, що остаточний час результату - це різниця у 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] -----------------------------------------------------

Я в кінцевому підсумку використовував mach_absolute_time()a mach_timebase_info_dataі потім зробив (long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));. Щоб отримати (void)mach_timebase_info(&your_timebase);
часову базу Mach

12
Згідно з документами для CFAbsoluteTimeGetCurrent (), "системний час може скорочуватися через синхронізацію із зовнішніми посиланнями часу або через явну зміну годинника користувачем". Я не розумію, чому хтось хотів би використовувати щось подібне для вимірювання минулого часу, якщо це може йти назад.
Крістофер Джонсон

6
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)

Використання:

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

Вихід:

2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023

NSDateзалежить від системних годин, які можуть бути змінені в будь-який час, що може призвести до неправильних або навіть негативних часових інтервалів. Використовуйте mach_absolute_timeнатомість, щоб отримати правильний час.
Cœur

mach_absolute_timeможе вплинути перезавантаження пристрою. Замість цього використовуйте час сервера.
келін

5

Крім того, ось як обчислити 64-розрядну NSNumberініціалізацію з епохою Unix в мілісекундах, на випадок, коли ви хочете зберегти її в CoreData. Мені це знадобилось для мого додатка, який взаємодіє із системою, яка зберігає дати таким чином.

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

3

Функції на основі mach_absolute_timeкорисні для коротких вимірювань.
Але для тривалих вимірювань важливим застереженням є те, що вони припиняють тикати, поки пристрій спить.

Існує функція отримати час після завантаження. Це не зупиняється під час сну. Крім того, gettimeofdayне є монотонним, але в своїх експериментах я завжди бачу, що час завантаження змінюється, коли змінюється системний час, тому я думаю, що він повинен працювати добре.

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
}

А оскільки iOS 10 та macOS 10.12 ми можемо використовувати 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
    }
}

Підсумовуючи це:

  • Date.timeIntervalSinceReferenceDate - зміни, коли змінюється системний час, а не монотонні
  • CFAbsoluteTimeGetCurrent() - не монотонне, може йти назад
  • CACurrentMediaTime() - припиняє тикати, коли пристрій спить
  • timeSinceBoot() - не спить, але може бути не монотонним
  • CLOCK_MONOTONIC - не спить, монотонно, підтримується з iOS 10

1

Я знаю, що це старе, але навіть я знову опинився, коли блукав повз нього, тому подумав, що подати тут свій варіант.

Найкраще перевірити мою публікацію в блозі на цю тему : Визначення часу в предметі-C: секундомір

В основному я написав клас, який перестає дивитися дуже просто, але інкапсульований так, що вам потрібно зробити лише наступне:

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

І закінчуєте:

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

в журналі ...

Ще раз, ознайомтеся з моєю публікацією ще трохи або завантажте її тут: MMStopwatch.zip


Ваша реалізація не рекомендується. NSDateзалежить від системних годин, які можуть бути змінені в будь-який час, що може призвести до неправильних або навіть негативних часових інтервалів. Використовуйте mach_absolute_timeнатомість, щоб отримати правильний час.
Cœur

1

Ви можете отримати поточний час у мілісекундах з 1 січня 1970 року за допомогою NSDate:

- (double)currentTimeInMilliseconds {
    NSDate *date = [NSDate date];
    return [date timeIntervalSince1970]*1000;
}

Це дає вам час у мілісекундах, але це все ще на секунду точності
dev

-1

Для тих, хто нам потрібна версія Swift, відповідь @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

Я сподіваюся, що це допоможе тобі.


2
NSDateзалежить від системних годин, які можуть бути змінені в будь-який час, що може призвести до неправильних або навіть негативних часових інтервалів. Використовуйте mach_absolute_timeнатомість, щоб отримати правильний час.
Cœur
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.