47

我正在寻找一种在 iOS 上获得绝对、始终增加的系统正常运行时间的方法。

它应该返回自设备上次重新启动以来的时间,并且不受系统日期更改的影响。

我能找到的所有方法要么在设备睡眠时暂停(CACurrentMediaTime, [NSProcessInfo systemUptime], mach_absolute_time),要么在系统日期更改时更改(sysctl/KERN_BOOTTIME)。

有任何想法吗?

4

6 回答 6

54

我想我解决了。

time()在设备休眠时继续递增,但当然可以由操作系统或用户操作。但是,内核启动时间(系统上次启动时间的时间戳)也会随着系统时钟的变化而变化,因此即使这两个值都不是固定的,它们之间的偏移量也是如此。

#include <sys/sysctl.h>

- (time_t)uptime
{
    struct timeval boottime;
    int mib[2] = {CTL_KERN, KERN_BOOTTIME};
    size_t size = sizeof(boottime);
    time_t now;
    time_t uptime = -1;

    (void)time(&now);

    if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0) {
        uptime = now - boottime.tv_sec;
    }

    return uptime;
}
于 2012-09-19T07:41:02.900 回答
26

正如其中一条评论所说,POSIX clock_gettime()是在 iOS 10 和 macOS 10.12 中实现的。当与CLOCK_MONOTONIC参数一起使用时,它似乎确实返回了正常运行时间值。但是,文档不能保证这一点:

对于此时钟,clock_gettime() 返回的值表示自过去某个未指定时间点(例如,系统启动时间或 Epoch)以来的时间量(以秒和纳秒为单位)。该点在系统启动时间后不会改变。

以及相应 macOS 手册页的摘录:

CLOCK_MONOTONIC单调递增的时钟,跟踪从任意点开始的时间,并在系统休眠时继续递增。

CLOCK_MONOTONIC_RAW单调递增的时钟,跟踪从任意点(如 CLOCK_MONOTONIC)开始的时间。但是,此时钟不受频率或时间调整的影响。不应将其与其他系统时间源进行比较。

CLOCK_MONOTONIC_RAW_APPROX与 CLOCK_MONOTONIC_RAW 类似,但在上下文切换时读取系统缓存的值。这可以更快地读取,但会损失准确性,因为它可能会返回毫秒前的值。

CLOCK_UPTIME_RAW单调递增的时钟,其方式与 CLOCK_MONOTONIC_RAW 相同,但在系统休眠时不会递增。在应用适当的 mach_timebase 转换后,返回的值与 mach_absolute_time() 的结果相同。

请注意,CLOCK_MONOTONIC以微秒精度返回,而CLOCK_MONOTONIC_RAW以纳秒精度返回。

SWIFT代码:

func uptime() -> timespec {

    var uptime = timespec()
    if 0 != clock_gettime(CLOCK_MONOTONIC_RAW, &uptime) {
        fatalError("Could not execute clock_gettime, errno: \(errno)")
    }

    return uptime
}

print(uptime()) // timespec(tv_sec: 636705, tv_nsec: 750700397)

对于那些仍然对在 Swift 中获取内核启动时间感兴趣的人:

func kernelBootTime() -> timeval {

    var mib = [ CTL_KERN, KERN_BOOTTIME ]
    var bootTime = timeval()
    var bootTimeSize = MemoryLayout<timeval>.size

    if 0 != sysctl(&mib, UInt32(mib.count), &bootTime, &bootTimeSize, nil, 0) {
        fatalError("Could not get boot time, errno: \(errno)")
    }

    return bootTime
}

print(kernelBootTime()) // timeval(tv_sec: 1499259206, tv_usec: 122778)
于 2017-07-12T21:47:27.403 回答
15

在列出的所有示例中都有一个竞争条件:如果有时间变化(NTP 或用户修改),代码将竞争并且时钟不再是单调的。

正确的实现是:

#include <sys/sysctl.h>

static int64_t us_since_boot() {
    struct timeval boottime;
    int mib[2] = {CTL_KERN, KERN_BOOTTIME};
    size_t size = sizeof(boottime);
    int rc = sysctl(mib, 2, &boottime, &size, NULL, 0);
    if (rc != 0) {
      return 0;
    }
    return (int64_t)boottime.tv_sec * 1000000 + (int64_t)boottime.tv_usec;
}

- (int64_t)us_uptime
{
    int64_t before_now;
    int64_t after_now;
    struct timeval now;

    after_now = us_since_boot();
    do {
        before_now = after_now;
        gettimeofday(&now, NULL);
        after_now = us_since_boot();
    } while (after_now != before_now);

    return (int64_t)now.tv_sec * 1000000 + (int64_t)now.tv_usec - before_now;
}
于 2016-11-08T22:54:16.890 回答
4

如果需要更高的精度,那么下面是接受答案的修改版本。

#include <sys/sysctl.h>

- (NSTimeInterval)uptime
{
    struct timeval boottime;
    int mib[2] = {CTL_KERN, KERN_BOOTTIME};
    size_t size = sizeof(boottime);

    struct timeval now;
    struct timezone tz;
    gettimeofday(&now, &tz);

    double uptime = -1;

    if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0)
    {
        uptime = now.tv_sec - boottime.tv_sec;
        uptime += (double)(now.tv_usec - boottime.tv_usec) / 1000000.0;
    }
    return uptime;
}
于 2016-03-18T17:50:38.077 回答
3

我会评论 Leszek 的回答,但没有足够的代表...... Leszek 的解决方案返回秒。

如果有人需要毫秒精度,以下是 Leszek 答案的修改版本。

#include <sys/sysctl.h>

+ (long long int)uptime 
{
    struct timeval boottime;
    int mib[2] = {CTL_KERN, KERN_BOOTTIME};
    size_t size = sizeof(boottime);

    struct timeval now;
    struct timezone tz;
    gettimeofday(&now, &tz);

    long long int uptime = -1;

    if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0)
    {
        uptime = ((long long int)(now.tv_sec - boottime.tv_sec)) * 1000;
        uptime += (now.tv_usec - boottime.tv_usec) / 1000;
    }
    return uptime;
}
于 2017-01-30T15:10:34.873 回答
-6

为什么不使用systemUptime

systemUptime 返回计算机重新启动后的时间。

  • (NSTimeInterval)systemUptime 返回值 一个 NSTimeInterval,指示计算机重新启动后的时间。

可用性 适用于 iOS 4.0 及更高版本。在 NSProcessInfo.h 中声明

我已经测试并证明,至少在带有 iOS 7.1.1 的 iPhone 5 上,当您的手机被锁定时,systemUptime 不会停止。所以任何人都不相信这可以自己测试。

于 2014-05-07T04:19:00.603 回答