闲话扯淡
好久没更新文章(估算有小半年了),之前上半年有段时间处于换工作空档期,比较闲,于是更新了好多篇文章,后来一忙起来,全身心投入到业务需求开发中,基本就没太多时间研究技术的东西了。
背景
这次发新版本的时候,改一个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代码如下:
里面再往里看能看到tv_sec是通过boottime_sec获取的,
boottime_sec的代码在/bsd/kern/kern_time.c中。
里面的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);
</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
查到的相关资料:
-
stackoverflow:有人问这个
mach_absolute_time
的真正含义,因为他发现自己的设备并没有重启,但是基于mach_absolute_time
算出来的时间,自己的设备在某一时刻重启过,所以对这个东西"一脸懵逼"...
http://stackoverflow.com/questions/1450737/... -
Apple 官方对某个开发者的提问What time units does it use?进行的回复.
https://developer.apple.com/library/content/qa/qa1398/... -
某位大牛的博客文章: mach_absolute_time使用
http://www.devdiv.com/mach_absolute_time...
网友评论