我正在寻找一种获取iOS系统绝对、始终递增的运行时间的方法。
该方法应返回设备上次启动以来的时间,不受系统日期更改的影响。
我能找到的所有方法都会在设备睡眠时暂停(CACurrentMediaTime
, [NSProcessInfo systemUptime]
, mach_absolute_time
)或者在系统日期更改时发生变化(sysctl/KERN_BOOTTIME
)。
有什么想法吗?
我想我已经弄清楚了。
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;
}
正如其中一条评论所说,POSIX的clock_gettime()
已经在iOS 10和macOS 10.12中实现。当使用CLOCK_MONOTONIC
参数时,它似乎确实返回了系统运行时间。然而,文档并未保证这一点:
对于该时钟,由
clock_gettime()
返回的值表示自过去某个不确定时间点以来(例如,系统启动时间或纪元),经过的时间(以秒和纳秒为单位)。此点在系统启动时间后不会改变。
以下是从相应的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)
CLOCK_MONOTONIC_RAW
会放弃时间?文档说明它是从一个未指定的任意时间点开始的。 - Gregory Pakosz所有示例中存在竞争条件:如果时间更改(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;
}
us_since_boot
。它只是启动时间,而不是自启动以来的经过时间。 - Keewon Seoboottime.tv_sec * 1000000
或 now.tv_sec * 1000000
时可能会发生溢出(在32位平台上很容易捕获)。为了避免这个问题,您必须进行直接转换。 - d12frosted#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;
}
我想评论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;
}
为什么不使用systemUptime呢?
systemUptime 返回自计算机重新启动以来的时间。
可用性 在iOS 4.0及更高版本中可用。 声明于 NSProcessInfo.h
我已经测试并证明,在iPhone 5上安装iOS 7.1.1时,当您的手机被锁定时,systemUptime并不会停止。所以任何人都可以亲自测试一下。