[iOS] 如何获取一个更为靠谱的当前时间

闲话扯淡

好久没更新文章(估算有小半年了),之前上半年有段时间处于换工作空档期,比较闲,于是更新了好多篇文章,后来一忙起来,全身心投入到业务需求开发中,基本就没太多时间研究技术的东西了。

背景

这次发新版本的时候,改一个bug时遇到一个时间戳的问题,想着好久没动笔了,就顺道把这东西记下来吧。
顺道立个flag:争取在17年到来之前,从这次开始,能多总结积累一些技术点,多出几篇文章。

需求

这次的需求非常简单,对于大部分童鞋估计扫一眼题目就知道123了,也不用再往后看了,我这里只当是个小技术点的记录吧。

言归正传,作为移动的App,平时向服务端发的所有request请求,作为基础数据,所有的request都应该带requestId和请求的时间戳以及设备信息,经纬度信息等等,这些应该作为默认选项。
关于时间戳,因为客户端的时间不是很靠谱(用户随时可能自己修改时间),所以原则上,客户端的时间仅仅作为参考。真正涉及DBA或是DB部门数据统计或计算的时候,必然已server端为准。
这里想说明的是,虽然条件有限,如何在有限的条件下,我们取一个尽量可以准确点的时间呢,这样当用户处于无网络或信号差的场景时,App需要执行打点记录或者某些进行一些需要时间的行为动作时,可以更加准确点。
前提是,用户可以随意修改时间,但是我们有木有办法绕过这个尴尬的现实,想个更巧妙的实现方式呢?

实现

1. 思路

首先,我们平时取时间的函数如下:

//ObjC
NSDate *date = [NSDate date];
// swift      
var date = NSDate()

那么这个方法不靠谱的话,只能再往深入一点想。
那就是直接获取底层的时间,想到的就是内核kernel的时间了。每次系统重启后,会一直记录启动至今的时间。

这个时间称之为upTime,我们获取server时间和uptime的差值timerOffset ,将这个值保存下来,后续每次发request请求的时候,基于timerOffset将upTime换算为上传的真正时间值即可,然后定时或者按照一定周期和服务端同步,将timerOffset进行修正。
其实简单点就是数学的a-b=c,a=b+c的问题,代码如下:

self.timerOffset = serverTimer - upTime;
NSTimeInterval uploadTime = upTime + self.timerOffset;

Okey,结构上来说还是比较清晰也比较简单的,唯一的难点在于获取启动时间Uptime,我们一步一步往下走。

2. 获取启动时间

2.1 ObjC/swift的方式获取

系统进程可以通过NSProcessInfo获取,然后他很好的提供给我们了一个Uptime接口,直接就可以获取到启动时间,代码如下:

//ObjC
NSTimeInterval systemUptime =[[NSProcessInfo processInfo] systemUptime];
//swift
var systemUptime = ProcessInfo().systemUptime;

这个获取到的时间但是是秒,打印一下,大概是这个样子:

(lldb) po [[NSProcessInfo processInfo] systemUptime]
11473.798027249

然后,然后,就完事了? 好像我们的需求就这么达到了?
嗯,至少看上去是这样,然后,直接写代码,git push了...
当然,理论上是这样的,
但是作为一个有想法的coder,怎么能就此打住呢,还是需要继续探索啊...

2.2 通过C的方式获取kernel_task的启动时间

2.2.1

time这个东西,不同于其他,原则上越精准越好,当然,对于大部分App来说,是基本没需求的,但是对于学习和既能提升来说,还是可以再往深里考虑考虑的。
首先OC或者swift来说,毕竟隔了一层编译,效率上毕竟和C或者Java还是有那么点差距,所以需要考虑有没有通过C的方式实现这个问题。

2.2.2

既然我这么问了,答案自然是有的。

"就是获取kernel_task的启动时间"

</br>

kernel_task是个系统级的task,用mac的人可能比较熟,很多人当电脑比较烫或者风扇转的比较响的时候,看一下活动监视器,就会看到kernel_task,如下截图:


屏幕快照 2016-10-28 下午5.24.03.png

kernel_task包括多线程调度管理、虚拟内存、系统IO、线程之间通信等等。所以系统已启动,kernel_task就会跑起来,kernel_task运行的时间,就可以作为启动时间来使用。

2.2.3

获取kernel_task的启动时间,我们需要用到一个函数sysctl, 首先看一下sysctl函数的定义:

int sysctl(int *, u_int, void *, size_t *, void *, size_t);
  • sysctl的第一参数是个数组,按照顺序第一个元素指定本请求定向到内核的哪个子系统,第二个及其后元素依次细化指定该系统的某个部分,类推...
  • 之后是数组的长度u_int.
  • 后面的size_t *需要注意,当sysctl被调用时,size指向的值指定该缓冲区的大小;函数返回时,该值给出内核存放在该缓冲区中的数据量,如果这个缓冲不够大,函数就返回ENOMEM错误
  • sysctl函数的结果:成功:返回0; 失败:返回-1

2.2.4

一言不合show me the code:

- (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;
}

具体sysctl的代码,可以看/bsd/kern/kern_sysctl.c
其中的sysctl_boottime函数可以获取boottime,sysctl_boottime代码如下:

屏幕快照 2016-10-29 上午12.44.54.png

里面再往里看能看到tv_sec是通过boottime_sec获取的,
boottime_sec的代码在/bsd/kern/kern_time.c中。

屏幕快照 2016-10-29 上午12.53.32.png

里面的clock_get_boottime_nanotime函数是在/osfmk/kern/clock.c中实现的。
屏幕快照 2016-10-29 上午12.55.20.png

这里插一句,如果大家去查sysctl,会查到sysctl在iOS9以后不可用的一些信息,这里我自己没直接找到相关的文档,但是我们这个时间的接口是可用的,iOS10亲测正常。唯一查到的相关信息是通过sysctl获取进程信息的那个路子被堵死了。
那个代码如下,大家不要搞混淆了.

    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL ,0};
    size_t miblen = 4;
    size_t size;
    int st = sysctl(mib, miblen, NULL, &size, NULL, 0);

想深入了解,看这里https://nabla...

</br>

2.3 还遗漏了一种方式mach_absolute_time

2.3.1

mach_absolute_time这个也是在查询sysctl相关信息的时候,意外发现的.
它不会直接告诉你一个时间,你可以理解为CPU进程依赖的一个函数,它会返回系统重启到现在的一个时钟“滴答”数,这个值自然是不能直接用的,毕竟是硬件层概念使用的一个参数,但是我们经过简单处理,就可以获取所需要的启动时间。

2.3.2

show me the code:

#include <mach/mach_time.h>

int getUptimeInMilliseconds()
{
    const int64_t kOneMillion = 1000 * 1000;
    static mach_timebase_info_data_t s_timebase_info;

    if (s_timebase_info.denom == 0) {
        (void) mach_timebase_info(&s_timebase_info);
    }

    // mach_absolute_time() returns billionth of seconds,
    // so divide by one million to get milliseconds
    return (int)((mach_absolute_time() * s_timebase_info.numer) / (kOneMillion * s_timebase_info.denom));
}

2.3.3

但是这个方法使用的人不多,原因在于,如果系统处于休眠状态,那么这个值也是停止的,所以它本质是进程运行的时钟计数器(run()),而非整个系统的(run() + sleep())时钟计数器。这样严格来说,是完全不准确的数据,不可用。
但具体相关我也没深入研究过,有了解过的童鞋,可以评论或私信给我,指教我一下。

这个较为多的使用场景是作为耗时的测量函数,比如在某个时间获取mach_absolute_time作为startTime,间隔一定时间之后再次获取mach_absolute_time作为endTime,然后差值可以作为这个阶段的耗时,这样的使用场景较多。

2.3.4

查到的相关资料:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 目标是开发一个SDK,嵌入到APP里面,用来统计当前APP的实时CPU、内存等信息 2015.11.17 http...
    953e9bf34714阅读 5,631评论 0 8
  • 如果你看完书中的所有例子,你很可能已经做完你的实验和在已经越狱的iPhone上的研究。因为和许多人一样,几乎所有的...
    fishmai0阅读 15,973评论 2 42
  • 1.NSLog(@"%@",[[UIDevice currentDevice] systemVersion]);/...
    Y像梦一样自由阅读 10,633评论 3 12
  • 场景 我们经常遇到这样的场景,比如电商类App到零点的时候开始抢购,比如商品限购倒计时等等。这种场景下需要我们将客...
    zhangbo1992阅读 4,702评论 10 39
  • 关键时刻,第一时间送达! 问题种类 时间复杂度 在集合里数据量小的情况下时间复杂度对于性能的影响看起来微乎其微。但...
    C9090阅读 893评论 0 1