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;
}
コメントの1つで述べたように、 POSIX'sclock_gettime()
はiOS 10およびmacOS 10.12で実装されました。 CLOCK_MONOTONIC
引数と共に使用すると、稼働時間の値を返すようです。ただし、これはドキュメントでは保証されていません。
このクロックの場合、clock_gettime()によって返される値は、過去の不特定の時点からの時間(秒およびナノ秒単位)を表します(たとえば、システムの起動時間やエポック)。この点は、システムの起動後は変わりません。
そして、対応するmacOSのmanページからの抜粋:
CLOCK_MONOTONICクロックは単調に増加し、任意のポイント以降の時間を追跡し、システムがスリープしている間も増加し続けます。
CLOCK_MONOTONIC_RAWクロックが単調に増加し、CLOCK_MONOTONICのような任意のポイント以降の時間を追跡します。ただし、このクロックは周波数や時間調整の影響を受けません。他のシステム時刻ソースと比較しないでください。
CLOCK_MONOTONIC_RAW_APPROXはCLOCK_MONOTONIC_RAWに似ていますが、コンテキストの切り替え時にシステムによってキャッシュされた値を読み取ります。これはより速く読み取ることができますが、ミリ秒前の値を返す可能性があるため、正確性が失われます。
CLOCK_MONOTONIC_RAWと同じ方法で単調に増加するCLOCK_UPTIME_RAWクロックですが、はそうではありませんシステムがスリープしている間にインクリメントします。戻り値は、適切なmach_timebase変換が適用された後のmach_absolute_time()の結果と同じです。
CLOCK_MONOTONIC
はマイクロ秒の精度で返され、CLOCK_MONOTONIC_RAW
はナノ秒の精度で返されることに注意してください。
スウィフトコード:
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)
リストされているすべての例に競合状態があります。時刻が変更された場合(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;
}
より高い精度が必要な場合は、承認された回答の修正版を以下に示します。
#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の回答の修正版です。
#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;
}