Как я могу получить точное время, например, в миллисекундах в Objective-C?


100

Есть ли простой способ очень точно узнать время?

Мне нужно рассчитать некоторые задержки между вызовами методов. В частности, я хочу рассчитать скорость прокрутки в UIScrollView.


вот очень похожий вопрос, который также может помочь понять ответы здесь ... пожалуйста, взгляните!
abbood 06


Ответы:


127

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

Чтобы рассчитать точность времени в миллисекундах, вы можете:

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

Документация по timeIntervalSinceNow .

Есть много других способов вычислить этот интервал с помощью NSDate, и я бы рекомендовал посмотреть документацию по классам, для NSDateкоторых можно найти в NSDate Class Reference .


3
На самом деле это достаточно точно для общего случая использования.
logancautrell 02

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

1
Я просто сравнил NSDateи mach_absolute_time()на уровне 30 мс. 27 против 29, 36 против 39, 43 против 45. NSDateМне было проще использовать, и результаты были достаточно похожи, чтобы не волноваться.
Nevan King

7
Использование NSDate для сравнения прошедшего времени небезопасно, поскольку системные часы могут измениться в любой момент (из-за NTP, переходов на летнее время, дополнительных секунд и многих других причин). Вместо этого используйте 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
Core Animation (QuartzCore.framework) также предоставляет удобный метод CACurrentMediaTime(), который преобразуется mach_absolute_time()непосредственно в файл 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), обновляющая часы (часто происходит с корректировкой дрейфа), настройки летнего времени, дополнительные секунды и т. Д.

Это означает, что если вы измеряете скорость загрузки или выгрузки, вы получите внезапные всплески или падения ваших чисел, которые не коррелируют с тем, что произошло на самом деле; ваши тесты производительности будут иметь странные неправильные выбросы; и ваши ручные таймеры сработают после неправильной продолжительности. Время может даже пойти в обратном направлении, и вы получите отрицательные дельты, и вы можете получить бесконечную рекурсию или мертвый код (да, я сделал и то, и другое).

Используйте 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 () и преобразования единиц в секунды»)
Невин

«может измениться в любое время по множеству различных причин, таких как синхронизация сетевого времени (NTP), обновление часов (часто происходит с корректировкой смещения), настройки летнего времени, дополнительные секунды и т. д.». - Какие еще могут быть причины? Я наблюдаю обратные скачки примерно на 50 мс при отсутствии подключения к Интернету. Таким образом, очевидно, что ни одна из этих причин не должна применяться ...
Фалько

@Falko не уверен, но если бы мне пришлось догадаться, я бы поспорил, что ОС сама компенсирует дрейф, если заметит, что разные аппаратные часы не синхронизированы?
Невин

15

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


2
Однако это значение с плавающей запятой двойной точности, которое обеспечивает точность до долей миллисекунды. Значение 72,89674947369 секунд не является необычным ...
Джим Дови,

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

3
@Jim: Есть ли у вас упоминание о том, что он обеспечивает точность менее миллисекунды (это честный вопрос)? Я очень надеюсь, что все здесь понимают разницу между точностью и точностью .
Адам Розенфилд,

5
CFAbsoluteTimeGetCurrent () вызывает gettimeofday () в OS X и GetSystemTimeAsFileTime () в Windows. Вот исходный код .
Джим Дови,

3
Да, и gettimeofday () реализован с использованием наносекундного таймера Маха через mach_absolute_time () ; вот источник общей страницы для реализации gettimeofday () на Darwin / ARM: opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
Джим Дави

10

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

Что бы я использовал:

CFAbsoluteTimeGetCurrent();

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

Что-то более интересное

Частное от разницы в mach_absolute_time()и AFAbsoluteTimeGetCurrent()всегда составляет около 24000011,154871.

Вот журнал моего приложения:

Пожалуйста , обратите внимание , что последний раз результата является разницей в CFAbsoluteTimeGetCurrent()

 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);
точную временную шкалу

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может повлиять на перезагрузку устройства. Вместо этого используйте серверное время.
kelin

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

Я знаю, что это старый, но даже я обнаружил, что снова блуждаю мимо него, поэтому я решил представить здесь свой собственный вариант.

Лучше всего проверить мою запись в блоге об этом: Расчет времени в Objective-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.