Вычисление количества секунд между двумя точками времени в Какао, даже когда системные часы изменились на полпути - PullRequest
10 голосов
/ 27 октября 2009

Я пишу программу для конечных пользователей Cocoa OS X (Leopard 10.5+), которая использует временные метки для расчета статистики того, как долго что-то отображается на экране. Время рассчитывается периодически, пока программа работает с использованием повторяющегося NSTimer. [NSDate date] используется для захвата меток времени, Старт и Конец . Вычисление разницы между двумя датами в секундах тривиально.

Проблема возникает, если конечный пользователь или NTP изменяет системные часы. [NSDate date] использует системные часы, поэтому, если они будут изменены, переменная Finish будет смещена относительно Start , что приведет к значительному искажению расчета времени. Мой вопрос:

1. Как я могу точно рассчитать время между Старт и Финиш в секундах, даже когда системные часы меняются на полпути?

Я думаю, что мне нужен неизменяемый опорный момент времени, чтобы я мог рассчитать, сколько секунд прошло с тех пор. Например, время работы системы. 10.6 имеет - (NSTimeInterval)systemUptime, часть NSProcessInfo, которая обеспечивает работоспособность системы. Тем не менее, это не будет работать, так как мое приложение должно работать в 10.5.

Я пытался создать счетчик времени с помощью NSTimer, но это не точно. NSTimer имеет несколько различных режимов запуска и может запускаться только по одному за раз. NSTimer (по умолчанию) переводится в режим запуска default . Если пользователь начинает манипулировать пользовательским интерфейсом в течение достаточно долгого времени, он введет NSEventTrackingRunLoopMode и пропустит режим запуска по умолчанию , что может привести к пропуску срабатываний NSTimer, что делает его неточным способ подсчета секунд.

Я также думал о создании отдельного потока (NSRunLoop) для запуска счетчика секунд NSTimer, не допуская его взаимодействия с пользовательским интерфейсом. Но я очень новичок в многопоточности, и я хотел бы избежать этого, если это возможно. Кроме того, я не уверен, что это будет работать точно в случае, если ЦП будет привязан другим приложением (Photoshop отображает большое изображение и т. Д.), В результате чего мой NSRunLoop будет удерживаться достаточно долго, чтобы испортить его NSTimer.

Я ценю любую помощь. :)

Ответы [ 3 ]

5 голосов
/ 27 октября 2009

В зависимости от того, что движет этим кодом, у вас есть 2 варианта:

  • Для абсолютной точности используйте mach_absolute_time(). Это даст точный интервал времени между точками, в которых вы вызвали функцию.
  • Но в приложении с графическим интерфейсом это часто нежелательно. Вместо этого вам нужна разница во времени между событиями , которые начались и закончили вашу продолжительность. Если это так, сравните [[NSApp currentEvent] timestamp]
2 голосов
/ 27 октября 2009

Хорошо, это длинный путь, но вы можете попробовать реализовать что-то вроде NSSystemClockDidChangeNotification, доступное в Snow Leopard.

Так что потерпите меня здесь, потому что это странная идея и определенно недерминированная. Но что, если у вас был сторожевой поток, работающий на протяжении всей вашей программы? Этот поток каждые n секунд считывает системное время и сохраняет его. Ради аргумента, давайте просто сделаем это 5 секунд. Таким образом, каждые 5 секунд он сравнивает предыдущее значение с текущим системным временем. Если есть «достаточно большая» разница («достаточно большая» должна определенно быть больше 5, но не слишком много больше, чтобы учесть недетерминированность планирования процессов и приоритезации потоков), напишите уведомление о существенном изменении времени. Вам нужно поиграть с размытым значением, которое составляет «достаточно большое» (или достаточно маленькое, если часы были сброшены на более раннее время) для вашей точности.

Я знаю, что это отчасти хакерство, но, за исключением любого другого решения, как вы думаете? Может ли это или что-то в этом роде решить вашу проблему?

Редактировать

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

И да, я знаю, что что-то такое, как Photoshop, связывающее дерьмо с процессором, является проблемой. Другое (даже более сложное) решение состоит в том, чтобы вместо сторожевого таймера thread иметь отдельный сторожевой таймер process , который имеет высший приоритет, поэтому он немного более защищен от привязки процессора. Но опять же, это становится действительно сложным.

Окончательное редактирование

Я собираюсь оставить все мои другие идеи выше для полноты картины, но, похоже, использование времени работы системы также будет правильным способом справиться с этим. Так как [[NSProcessInfo processInfo] systemUptime] работает только в 10.6+, вы можете просто позвонить mach_absolute_time(). Чтобы получить доступ к этой функции, просто #include <mach/mach_time.h>. Это должно быть то же значение, которое возвращается NSProcessInfo.

1 голос
/ 27 октября 2009

Я нашел способ сделать это с помощью функции UpTime () C, представленной в <CoreServices/CoreServices.h>. Это возвращает абсолютное время (зависит от процессора), которое может быть легко преобразовано в длительность (миллисекунды или наносекунды). Подробности здесь: http://www.meandmark.com/timingpart1.html (смотрите UpTime в части 3)

Я не смог заставить mach_absolute_time() работать должным образом, вероятно, из-за того, что у меня не было об этом знаний, и из-за того, что я не мог найти в Интернете много документации об этом. Кажется, он захватывает то же самое время, что и UpTime(), но превращение его в двойное оставило меня ошеломленным.

[[NSApp currentEvent] timestamp] работал, но только если приложение получало NSEvents. Если приложение вышло на передний план, оно не получало бы события, и [[NSApp currentEvent] timestamp] просто продолжал бы снова и снова возвращать одну и ту же старую метку времени в методе запуска NSTimer, пока конечный пользователь не решил снова взаимодействовать с приложением.

Спасибо за вашу помощь, Марк и Майк! Вы оба определенно отправили меня в правильном направлении, что привело к ответу. :)

...