【问题标题】:How can I get a precise time, for example in milliseconds in Objective-C?如何获得精确的时间,例如在 Objective-C 中以毫秒为单位?
【发布时间】:2010-10-27 17:24:06
【问题描述】:

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

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

【问题讨论】:

标签: iphone objective-c cocoa-touch uikit


【解决方案1】:

NSDatetimeIntervalSince* 方法将返回一个 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;

Documentation on timeIntervalSinceNow.

还有许多其他方法可以使用NSDate 计算此间隔,我建议您查看NSDate 的类文档,该文档位于NSDate Class Reference

【讨论】:

  • 其实这对于一般用例来说已经足够精确了。
  • 使用 NSDates 计算经过时间是否安全?在我看来,系统时间在同步到外部时间源时可以向前或向后,所以你不能相信结果。你真的想要一个单调递增的时钟,不是吗?
  • 我刚刚比较了 NSDatemach_absolute_time() 在大约 30 毫秒的水平。 27 vs. 29, 36 vs. 39, 43 vs. 45。NSDate 对我来说更容易使用,结果非常相似,我不在乎。
  • 使用 NSDate 比较经过的时间是不安全的,因为系统时钟可以随时更改(由于 NTP、DST 转换、闰秒和许多其他原因)。请改用 mach_absolute_time。
  • @nevyn 你刚刚保存了我的网速测试,你!该死的,我很高兴我在复制粘贴代码之前阅读了 cmets 呵呵
【解决方案2】:

mach_absolute_time() 可用于进行精确测量。

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

还可以使用CACurrentMediaTime(),它本质上是相同的,但具有更易于使用的界面。

(注意:此答案是在 2009 年编写的。请参阅 Pavel Alexeev 的答案,了解较新版本的 macOS 和 iOS 中可用的更简单的 POSIX clock_gettime() 接口。)

【讨论】:

  • 该代码是一个奇怪的转换 - 第一个示例的最后一行是“return * (uint64_t *) &elapsedNano;”为什么不只是“返回 (uint64_t)elapsedNano”?
  • Core Animation (QuartzCore.framework) 还提供了一个方便的方法CACurrentMediaTime(),将mach_absolute_time()直接转换为double
  • @Tyler, elapsedNanoNanoseconds 类型,它不是纯整数类型。它是UnsignedWide 的别名,它是一个具有两个 32 位整数字段的结构。如果您愿意,可以使用UnsignedWideToUInt64() 代替演员表。
  • CoreServices 只是一个 Mac 库。 iOS 中是否有等价物?
  • @mm24 在 iOS 上,使用核心动画中的 CACurrentMediaTime()。
【解决方案3】:

请不要使用NSDateCFAbsoluteTimeGetCurrentgettimeofday 来测量经过的时间。这些都取决于系统时钟,由于许多不同的原因,系统时钟可能会在任何时间发生变化,例如网络时间同步 (NTP) 更新时钟(经常针对漂移进行调整)、DST 调整、闰秒,等等。

这意味着,如果您正在测量下载或上传速度,您的数字会突然出现峰值或下降,这与实际发生的情况无关;你的性能测试会有奇怪的错误异常值;并且您的手动计时器将在不正确的持续时间后触发。时间甚至可能倒退,最终得到负增量,最终得到无限递归或死代码(是的,我已经完成了这两个)。

使用mach_absolute_time。它测量内核启动后的实际秒数。它是单调递增的(永远不会倒退),并且不受日期和时间设置的影响。由于使用起来很痛苦,所以这里有一个简单的包装器,可以为您提供NSTimeIntervals:

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

【讨论】:

  • 谢谢。 QuartzCore 的CACurrentMediaTime() 怎么样?
  • 注意:也可以用@import Darwin;代替#include &lt;mach/mach_time.h&gt;
  • @Cœur CACurrentMediaTime 实际上应该与我的代码完全相同。好发现! (标题说“这是调用mach_absolute_time()并将单位转换为秒的结果”)
  • "可能会因为许多不同的原因而随时更改,例如网络时间同步 (NTP) 更新时钟(经常针对漂移进行调整)、DST 调整、闰秒等。 " - 那可能是什么其他原因?我在没有互联网连接的情况下观察到大约 50 毫秒的向后跳跃。所以,显然,这些理由都​​不应该适用......
  • @Falko 不确定,但如果我不得不猜测,我敢打赌,如果操作系统注意到不同的硬件时钟不同步,它会自行进行漂移补偿?
【解决方案4】:

CFAbsoluteTimeGetCurrent() 将绝对时间作为double 值返回,但我不知道它的精度是多少——它可能每十毫秒更新一次,或者它可能每微秒更新一次,我不知道。

【讨论】:

  • 虽然它是一个双精度浮点值,但确实提供了亚毫秒级的精度。 72.89674947369 秒的值并不少见...
  • @JimDovey:我不得不说72.89674947369 秒的值非常罕见,考虑到它可能的所有其他值。 ;)
  • @Jim:你有没有引用它提供亚毫秒精度(这是一个诚实的问题)?我希望这里的每个人都能理解accuracy and precision 之间的区别。
  • CFAbsoluteTimeGetCurrent() 在 OS X 上调用 gettimeofday(),在 Windows 上调用 GetSystemTimeAsFileTime()。这是the source code
  • 哦,gettimeofday() 是通过mach_absolute_time() 使用马赫纳秒计时器实现的;这是 Darwin/ARM 上 gettimeofday() 的公共页面实现的源代码:opensource.apple.com/source/Libc/Libc-763.12/arm/sys/…
【解决方案5】:

我不会使用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()mach_timebase_info_data,然后使用(long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));。要获取马赫时基,可以(void)mach_timebase_info(&amp;your_timebase);
  • 根据 CFAbsoluteTimeGetCurrent() 的文档,“系统时间可能会由于与外部时间参考同步或由于用户明确更改时钟而减少。”我不明白为什么有人会想用这样的东西来测量经过的时间,如果它可以倒退的话。
【解决方案6】:

基于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 起支持

【讨论】:

    【解决方案7】:

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

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

    【讨论】:

      【解决方案8】:
      #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 可能会受到设备重启的影响。请改用服务器时间。
      【解决方案9】:

      我知道这是一个旧的,但即使我发现自己再次徘徊,所以我想我会在这里提交我自己的选择。

      最好的办法是查看我的博客文章: Timing things in Objective-C: A stopwatch

      基本上,我写了一个类,它确实以非常基本的方式停止观看,但被封装,因此您只需要执行以下操作:

      [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 来获取正确的经过时间。
      【解决方案10】:

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

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

      【讨论】:

      • 这会给你以毫秒为单位的时间,但它仍然是秒精度
      【解决方案11】:

      对于那些我们需要@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
      

      希望对你有所帮助。

      【讨论】:

      • NSDate 取决于系统时钟,可以随时更改,可能导致错误甚至负时间间隔。请改用mach_absolute_time 来获取正确的经过时间。
      猜你喜欢
      • 2019-08-29
      • 1970-01-01
      • 1970-01-01
      • 2015-07-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多