Время истекло в Objective-C


153

Мне нужно, чтобы прошло время между двумя событиями, например, появлением UIView и первой реакцией пользователя.

Как я могу достичь этого в Objective-C?

Ответы:


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

timeInterval это разница между началом и теперь, в секундах, с точностью до миллисекунды.


18
@NicolasZozol вы можете использовать, fabs(...)чтобы получить абсолютное значение с плавающей точкой. Например. NSTimeInterval timeInterval = fabs([start timeIntervalSinceNow]);
Итак,

1
Кажется, значение timeInterval является отрицательным.
Джеки,

если вы получаете отрицательное значение - умножьте на -1, и все хорошо
PinkFloydRocks

10
Полагаться на это [NSDate date]может привести к трудностям в отслеживании ошибок, см. Этот ответ для получения дополнительной информации.
Чувственный

@PinkFloydRocks - Что? Как отрицательное значение когда-либо законно произойдет?
Тодд Леман

228

Вы не должны полагаться на [NSDate date]сроки, поскольку это может привести к завышению или занижению прошедшего времени. Есть даже случаи, когда ваш компьютер будет путешествовать во времени, поскольку прошедшее время будет отрицательным! (Например, если часы переместились назад во время синхронизации.)

Согласно Арии Хагиги в лекции «Расширенное распознавание жестов iOS» курса iOS 2013 года в Стэнфорде (34:00), вы должны использоватьCACurrentMediaTime() если вам нужен точный интервал времени.

Objective-C:

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

Swift:

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

Причина в том, что [NSDate date]синхронизация на сервере такова, что это может привести к «сбоям синхронизации по времени», что может привести к очень трудным для отслеживания ошибкам.CACurrentMediaTime()с другой стороны, это время устройства, которое не изменяется при этих сетевых синхронизации.

Вам нужно будет добавить платформу QuartzCore в настройки вашей цели.


17
Пожалуйста, проголосуйте за это. Хотя я думаю, что все могут согласиться с тем, что NSDate должен быть вашим первым вариантом, это предложение решило мою проблему. При вызове [NSDate date]внутри блока завершения в блоке отправки я получал то же самое «текущее» время, о котором сообщалось [NSDate date]непосредственно перед тем, как выполнение достигло точки, в которой были созданы мои блоки. CACurrentMediaTime()решил эту проблему.
n00neimp0rtant

Как бы мы использовали elapsedTime для отображения как mm: ss?
CyberMew

@CyberMew: Это отдельная проблема. Видеть Как мне разбить NSTimeInterval на год, месяцы, дни, часы, минуты и секунды на iPhone? для некоторых решений.
Сенс

12
Имейте в CACurrentMediaTime()виду, что перестает тикать, когда устройство переходит в режим сна. Если вы тестируете устройство, отключенное от компьютера, заблокируйте его, а затем подождите ~ 10 минут, и вы обнаружите, что CACurrentMediaTime()оно не идет в ногу со временем настенных часов.
русский епископ

добавить и импортировать #import <QuartzCore / QuartzCore.h>
Стивен Золеко

23

Используйте timeIntervalSinceDateметод

NSTimeInterval secondsElapsed = [secondDate timeIntervalSinceDate:firstDate];

NSTimeIntervalэто просто doubleопределить, NSDateкак это:

typedef double NSTimeInterval;

15

Для тех, кто приезжает сюда в поисках реализации getTickCount () для iOS, вот мое после объединения различных источников.

Ранее у меня была ошибка в этом коде (сначала я разделил на 1000000), которая приводила к некоторому количественному определению выходных данных на моем iPhone 6 (возможно, это не было проблемой на iPhone 4 / etc или я просто никогда этого не замечал). Обратите внимание, что если сначала не выполнить это деление, существует некоторый риск переполнения, если числитель временной базы достаточно велик. Если кому-то интересно, здесь есть ссылка с гораздо большей информацией: https://stackoverflow.com/a/23378064/588476

В свете этой информации, возможно, безопаснее использовать функцию Apple CACurrentMediaTime !

Я также mach_timebase_infoпротестировал вызов, и на моем iPhone 6 это заняло приблизительно 19 нс, поэтому я удалил (не поддерживающий потоки) код, который кэшировал выходные данные этого вызова.

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

Помните о потенциальном риске переполнения в зависимости от выходных данных вызова временной базы. Я подозреваю (но не знаю), что она может быть постоянной для каждой модели iPhone. на моем айфоне 6 было 125/3.

Использование решения CACurrentMediaTime()довольно тривиально:

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

Кто-нибудь знает, почему при вызове mach_timebase_info происходит избыточное (пустое) приведение?
Саймон Тилсон

@SimonTillson - это хороший вопрос - либо нужно было заставить предупреждение замолчать, либо я скопировал его откуда-то еще и просто не заметил, чтобы удалить его. Я не могу вспомнить
Уэйн Урода

2
Это не потокобезопасно. mach_timebase_info()сбросит переданный в mach_timebase_info_data_tперед {0,0}тем, как установить его в фактические значения, что может вызвать деление на ноль, если код вызывается из нескольких потоков. Используйте dispatch_once()вместо этого (или CACurrentMediaTimeкоторое является просто временем Маха, преобразованным в секунды).
wonder.mice

Спасибо @ wonder.mice, я обновил свой ответ (также нашел проблему, касающуюся количественного определения, я виню меня на 6 лет моложе меня ...)
Уэйн Урода


4

используйте функцию timeIntervalSince1970 класса NSDate, как показано ниже:

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

в основном, это то, что я использую, чтобы сравнить разницу в секундах между двумя разными датами. также проверьте эту ссылку здесь


0

Другие ответы верны (с оговоркой *). Я добавляю этот ответ просто, чтобы показать пример использования:

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

На консоли отладчика вы видите что-то вроде этого:

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

* Предостережение: как уже упоминалось, используйте NSDateдля расчета прошедшего времени только для случайных целей. Одной из таких целей может быть обычное тестирование, грубое профилирование, когда вы просто хотите получить приблизительное представление о том, сколько времени занимает метод.

Риск состоит в том, что текущая установка времени устройства может измениться в любой момент из-за синхронизации сетевых часов. Так что NSDateвремя может прыгать вперед или назад в любой момент.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.