如何获得精确的时间,例如在Objective-C中以毫秒为单位?


99

有没有一种简单的方法可以精确地获取时间?

我需要计算两次方法调用之间的延迟。更具体地说,我想计算UIScrollView中的滚动速度。


是一个非常相关的问题,也可能有助于了解此处的答案。请看一下!
2012年


Answers:


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;

timeIntervalSinceNow上的文档

还有许多其他方法可以使用来计算此间隔NSDate,我建议您查看《NSDate类参考》NSDate中的类文档


3
实际上,对于一般用例而言,这足够精确。
logancautrell

4
使用NSDates计算经过时间是否安全?在我看来,与外部时间源同步时,系统时间可以前进或后退,因此您将无法信任结果。您真的想要一个单调递增的时钟,不是吗?
克里斯托弗·约翰逊

1
我只是比较NSDatemach_absolute_time()大约30毫秒的水平。27 vs. 29、36 vs. 39、43 vs. 45. NSDate对我来说更容易使用,结果相似到足以忽略。
2013年

7
使用NSDate来比较经过的时间是不安全的,因为系统时钟可以随时更改(由于NTP,DST转换,leap秒和许多其他原因)。请改用mach_absolute_time。
nevyn 2015年

@nevyn,您刚刚保存了我的互联网速度测试,ty!该死,我很高兴在复制粘贴代码之前阅读了注释,呵呵
艾伯特·伦肖

41

mach_absolute_time() 可用于获得精确的测量值。

参见http://developer.apple.com/qa/qa2004/qa1398.html

也可以使用CACurrentMediaTime(),它基本上是相同的东西,但是具有易于使用的界面。

(注意:此答案写于2009年。有关clock_gettime()新版macOS和iOS中可用的更简单POSIX 接口的信息,请参阅Pavel Alexeev的答案。)


1
在该代码中有一个怪异的转换-第一个示例的最后一行是“ return *(uint64_t *)&elapsedNano;”。为什么不只是“返回(uint64_t)lapsedNano”?
泰勒(Tyler)2010年

8
核心动画(QuartzCore.framework)还提供了一种便捷方法CACurrentMediaTime(),该方法可以mach_absolute_time()直接转换为double
otto

1
@Tyler elapsedNano是类型Nanoseconds,不是普通的整数类型。这是的别名UnsignedWide,它是具有两个32位整数字段的结构。UnsignedWideToUInt64()如果愿意,可以使用而不是演员表。
肯·托马斯

CoreServices只是一个Mac库。iOS中是否有等效功能?
mm24

1
@ mm24在iOS上,使用Core Animation中的CACurrentMediaTime()。
克里斯托弗·约翰逊

28

请不要使用NSDateCFAbsoluteTimeGetCurrentgettimeofday测量经过的时间。这些都依赖于系统时钟,它可以在改变任何时间,由于许多不同的原因,诸如网络时间同步(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?
2015年

1
注:一个也可以使用@import Darwin;,而不是#include <mach/mach_time.h>
心教堂

@CœurCACurrentMediaTime实际上应该与我的代码完全等效。好发现!(标题说“这是调用mach_absolute_time()并将单位转换为秒的结果”)
nevyn

“由于许多不同的原因,可以随时更改,例如网络时间同步(NTP)更新时钟(经常为调整漂移而调整),DST调整,leap秒等等。” -那还有什么其他原因?没有网络连接时,我观察到大约50毫秒的向后跳动。因此,显然,这些理由都​​不应该适用...
Falko

@Falko不确定,但是如果我不得不猜测的话,我敢打赌,如果操作系统注意到不同的硬件时钟不同步,它会自行补偿漂移吗?
nevyn

14

CFAbsoluteTimeGetCurrent()返回绝对时间作为double值,但是我不知道它的精度是什么-它可能仅每十几毫秒更新一次,或者它可能每毫秒更新一次,我不知道。


2
但是,它是双精度浮点值,并且确实提供了亚毫秒级的精度。72.89674947369秒的值并不罕见……
Jim Dovey

25
@JimDovey:72.89674947369考虑到所有其他值,我不得不说秒的值非常少见。;)
FreeAsInBeer 2012年

3
@Jim:您是否引用它提供毫秒以下的精度(这是一个老实的问题)?我希望这里的每个人都能理解准确性和准确性之间的区别。
亚当·罗森菲尔德

5
CFAbsoluteTimeGetCurrent()在OS X上调用gettimeofday(),在Windows上调用GetSystemTimeAsFileTime()。这是源代码
Jim Dovey 2012年

3
哦,gettimeofday()是通过mach_absolute_time()使用马赫纳秒计时器实现的;这里是对达尔文/ ARM共同页实现的gettimeofday()来源: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()

 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);
Nate Symer 2012年

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而是使用它来获取正确的经过时间。
心教堂

mach_absolute_time可能会受到设备重启的影响。请改用服务器时间。
凯林2015年

5

此外,如果要NSNumber在Unix纪元中将其存储在CoreData中,这是如何计算以毫秒为单位的Unix纪元初始化的64位。我的应用程序需要这种方式,它可以与以这种方式存储日期的系统进行交互。

  + (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而是使用它来获取正确的经过时间。
心教堂

1

您可以使用NSDate获取自1970年1月1日以来的当前时间(以毫秒为单位):

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

这为您提供了以毫秒为单位的时间,但仍以秒为单位精度
dev

-1

对于那些人,我们需要@Jeff Thompson的答案的Swift版本:

// 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而是使用它来获取正确的经过时间。
心教堂
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.