iOS关于时间的拍卖

By admin in 天文台 on 2019年2月10日

做App避免不了要和时间打交道,关于时间的处理,里面有成百上千门路,远不是单排API调用,获取当前系统时间这么简单。大家必要精晓与时间相关的种种API之间的歧异,再因场合而异去设计相应的体制。

日子的样式

在上马深刻座谈此前,我们要求确信一个前提:时光是线性的。即自由一个随时,这么些地球上唯有一个万万时间值存在,只不过因为时区或者文化的出入,处于同一时空的大家对同一时间的发挥依旧了然分裂。那一个类似简单明了的道理,是我们清楚种种与时间相关的扑朔迷离概念的木本。就好像UTF-8和UTF-16其实都是Unicode一样,巴黎的20:00和日本东京的21:00其实是同一个纯属的时间值。

GMT

人类对于时间的知情还很容易,但大家起码能确定一点:时间的转移是匀速的。时间发展的进度是均匀的,不会忽快忽慢,所以为了描述时间,大家也急需找到一个值,它的转变也是以均匀的快慢前进变化的。

说出去您或许不信,大家人类为了摸索这几个参考值,来规范描述当前的岁月值,都经历了漫长岁月的探赜索隐。你可以尝尝思考下,生活中有如何事物是随着时间均匀变化的,它装有的数值属性,会趁着岁月处在相对的匀速变化意况。

前任发现抬头看太阳是个好方法,太阳总是按常理的“早起晚落”,而且“亘古不变”,可以用阳光在一天当中所处的岗位来叙述当前的小时。后来差别地段的知识要求沟通,你那里太阳正高空照,我那说不定曾经下山了,所以要求有一个国有的门阀都认账的地方,以那些地方太阳的任务来做参考着,沟通起来就会方便广大。最终选项的是英国London的格林尼治天文台所在地,以格林尼治的年华作为国有时间,也就是大家所说的GMT时间(格林wich
Mean 提姆e)。

UTC

阳光所处的职分变动跟地球的自转相关,过去人们觉得地球自转的速率是永恒的,但在1960年这一认知被推翻了,人们发现地球自转的速率正变得愈加慢,而时间发展的速率照旧稳定的,所以GMT不再被认为可以用来精准的讲述时间了。

我们必要一而再寻找一个匀速前进的值。抬头看天是我们从宏观方向去探寻答案,科学技术的发展让大家在微观方面取得了更深的认识,于是有聪明人按照微观粒子原子的物理属性,建立了原子钟,以那种原子钟来衡量时间的扭转,原子钟50亿年才会误差1秒,那种精读已经远胜于GMT了。那个原子钟所体现的日子,也就是我们明日所运用的UTC(Coordinated
Universal Time )标准时间。

接下去大家看下iOS里,五花八门的记录时间的情势。

NSDate

NSDate是我们平时利用较多的一个类,先看下它的概念:

NSDate objects encapsulate a single point in time, independent of
any particular calendrical system or time zone. Date objects are
immutable, representing an invariant time interval relative to an
absolute reference date (00:00:00 UTC on 1 January 2001).

NSDate对象描述的是光阴线上的一个相对的值,和时区和学识无关,它参考的值是:以UTC为规范的,2001年元月一日00:00:00这一刻的岁月相对值。

此间有个概念很关键,我们用编程语言叙述时间的时候,都是以一个时间线上的绝对值为参考点,参考点再加上偏移量(以秒或者飞秒,飞秒,皮秒为单位)来描述此外的时间点。

接头了那或多或少,再看NSDate的有些API调用就尤其驾驭了,比如:

NSDate* date = [NSDate date];
NSLog(@"current date interval: %f", [date timeIntervalSinceReferenceDate]);

timeIntervalSinceReferenceDate回到的是距离参考时间的偏移量,这些偏移量的值为502945767秒,502945767/86400/365=15.9483056507,86400是一天所涵盖的秒数,365大致是一年的命局,15.94本来就是年数了,算出来刚好是此时相差2001年的差值。

又比如说,此刻我写文章的时候,当前时间为新加坡时间清晨11:29,看看上边代码的出口:

NSDate* date = [NSDate date];
NSLog(@"current date: %@", date);

current date: 2016-12-09 03:29:09 +0000。可知NSDate输出的是纯属的UTC时间,而新加坡时间的时区为UTC+8,上边的输出+8个钟头,刚好就是我当下的小时了。

NSDate和市区和学识毫无干系,所以要来得具体格式的日子,大家需求NSDateFormatterNSTimeZone的辅助。

除此以外关于NSDate最要紧的一点是:NSDate是受手机系统时间决定的。也就是说,当您改改了手机上的时间显示,NSDate获取当今天子的输出也会跟着变动。在我们做App的时候,精通那一点,就通晓NSDate并不牢靠,因为用户可能会修改它的值。

CFAbsoluteTimeGetCurrent()

官方概念如下:

Absolute time is measured in seconds relative to the absolute
reference date of Jan 1 2001 00:00:00 GMT. A positive value represents
a date after the reference date, a negative value represents a date
before it. For example, the absolute time -32940326 is equivalent to
December 16th, 1999 at 17:54:34. Repeated calls to this function do
not guarantee monotonically increasing results. The system time may
decrease due to synchronization with external time references or due
to an explicit user change of the clock.

从地方的叙说简单看出CFAbsolute提姆eGetCurrent()的定义和NSDate非凡相似,只然则参考点是:以GMT为正式的,2001年6月一日00:00:00这一阵子的时日相对值。

同等CFAbsoluteTimeGetCurrent()也会跟着当前设备的系列时间一起变化,也恐怕会被用户修改。

gettimeofday

那些API也能回来一个描述当前岁月的值,代码如下:

struct timeval now;
struct timezone tz;
gettimeofday(&now, &tz);
NSLog(@"gettimeofday: %ld", now.tv_sec);

选择gettimeofday得到的值是Unix time。Unix time又是怎么着呢?

Unix time是以UTC 1970年10月1号
00:00:00为标准时间,当前时刻相差基准点偏移的秒数。上述API再次回到的值是1481266031,表示近期岁月相差UTC
1970年二月1号 00:00:00一共过了1481266031秒。

Unix
time也是日常大家运用较多的一个小时标准,在Mac的极端可以通过以下命令转换成可观察的岁月:

date -r 1481266031

事实上NSDate也有一个API能再次来到Unix time:

NSDate* date = [NSDate date];
NSLog(@"timeIntervalSince1970: %f", [date timeIntervalSince1970]);

gettimeofday和NSDate,CFAbsolute提姆eGetCurrent()一样,都是受当前设备的系统时间影响。只不过是参考的年华基准点分歧等而已。我们和服务器通信的时候一般采用Unix
time。

mach_absolute_time()

mach_absolute_time()可能用到的同桌相比较少,但以此概念分外紧要。

面前提到我们必要找到一个均匀变化的属性值来叙述时间,而在大家的华为上刚刚有一个这么的值存在,就是CPU的钟表周期数(ticks)。这一个tick的数值可以用来叙述时间,而mach_absolute_time()重回的就是CPU已经运行的tick的多少。将以此tick数经过一定的转换就可以变成秒数,或者阿秒数,那样就和岁月一直涉及了。

不过这么些tick数,在每一遍手机重启之后,会再度初阶计数,而且酷派锁屏进入休眠之后tick也会暂停计数。

mach_absolute_time()不会受系统时间影响,只受设备重启和休眠行为影响。

CACurrentMediaTime()

CACurrentMedia提姆e()可能接触到的同学会多一些,先看下官方概念:

/* Returns the current CoreAnimation absolute time. This is the result of
 * calling mach_absolute_time () and converting the units to seconds. */
CFTimeInterval CACurrentMediaTime (void)

CACurrentMedia提姆e()就是将下边mach_absolute_time()的CPU
tick数转化成秒数的结果。以下代码:

double mediaTime = CACurrentMediaTime();
NSLog(@"CACurrentMediaTime: %f", mediaTime);

归来的就是开机后装置累计运行了(设备休眠不计算在内)多少秒,另一个API也能回去相同的值:

NSTimeInterval systemUptime = [[NSProcessInfo processInfo] systemUptime];
NSLog(@"systemUptime: %f", systemUptime);

CACurrentMedia提姆e()也不会受系统时间影响,只受设备重启和休眠行为影响。

sysctl

iOS系统还记下了上次设备重启的年月。可以由此如下API调用获取:

#include <sys/sysctl.h>

- (long)bootTime
{
#define MIB_SIZE 2
    int mib[MIB_SIZE];
    size_t size;
    struct timeval  boottime;

    mib[0] = CTL_KERN;
    mib[1] = KERN_BOOTTIME;
    size = sizeof(boottime);
    if (sysctl(mib, MIB_SIZE, &boottime, &size, NULL, 0) != -1)
    {
        return boottime.tv_sec;
    }
    return 0;
}

再次回到的值是上次设备重启的Unix time。

那几个API重返的值也会受系统时间影响,用户一旦改动时间,值也会随着变化。

有了上述得到时间的各类招数,大家再来看看一些风貌之下的现实性行使。

场景一,时间测量

咱俩做品质优化的时候,日常必要对某个方法执行的光阴做笔录,就决然会用到地点提到的有的得到时间的章程。

在做方法执行时间的benchmark的时候,我们取得时间的方式要餍足三个要求,一是精读要高,而是API本身大约不耗CPU时间。

客户端做质量优化一般是为着主线程的流畅性,而大家领悟UI线程即使遇到当先16.7ms的阻隔,就会出现掉帧现象,所以大家关怀的时刻的精读实际上是在阿秒(ms)级别。大家写客户端代码的时候,基本上都是居于ms这一维度,即使一个艺术损耗是0.1ms,大家可以认为那么些格局对于流畅性来说是高枕无忧的,倘诺平时见到超越1ms或者多少个ms的措施,主线程出现卡顿的几率就会变高。

上面二种得到时间的法子精读上都是十足的,比如一个NSDateAPI调用重回的精读是0.000004
S
,也就是4微秒,CACurrentMediaTime()回去的精读也在阿秒级别,精读上都符合必要。不过有一种看法,认为NSDate属于类的包装,OOP高级语言本身所带来的损耗可能会潜移默化最终的其实结果,在做benchmark的时候不如C函数调用精准,为了证实这一说法,我写了一段简单的测试代码:

int testCount = 10000;
double avgCost = 0;
for (int i = 0; i < testCount; i ++) {
    NSDate* begin = [NSDate date];
    NSLog(@"a meaningless log");
    avgCost += -[begin timeIntervalSinceNow];
}
NSLog(@"benchmark with NSDate: %f", avgCost/testCount);

avgCost = 0;
for (int i = 0; i < testCount; i ++) {
    double startTime = CACurrentMediaTime();
    NSLog(@"a meaningless log");
    double endTime = CACurrentMediaTime();
    avgCost += (endTime - startTime);
}
NSLog(@"benchmark with CACurrentMediaTime: %f", avgCost/testCount);

出口结果为:

benchmark with NSDate: 0.000046
benchmark with CACurrentMediaTime: 0.000037

可以看看CACurrentMedia提姆e与NSDate代码本身的费用差别在几飞秒,而大家做UI质量优化的维度在毫秒级别,几个飞秒的出入完全不会潜移默化大家最终的判断结果。所以选择NSDate做benchmark完全是行得通的,以下是自己常用的五个宏:

#define TICK   NSDate *startTime = [NSDate date]
#define TOCK   NSLog(@"Time Cost: %f", -[startTime timeIntervalSinceNow])

情状二:客户端和服务器之间的时刻共同

那也是我们平常碰到的现象,比如电商类App到零点的时候开始抢购,比如商品限购倒计时等等,那种光景下须求我们将客户端的时间与服务器保持一致,最要紧的是,要防患用户通过断网修改系统时间,来震慑客户端的逻辑。

正如常见的做法是,在有的常用的Server接口里面带上服务器时间,每调用三回接口,客户端就和服务器时间做四回联合并记录下来,但难题是什么预防用户修改呢?

下面提到的NSDate,CFAbsolute提姆eGetCurrent,gettimeofday,sysctl都是跟随系统时间变化的,mach_absolute_time和CACurrentMedia提姆e即使是依照CPU时钟数,不受系统时间影响,但在蛰伏和重启的时候照旧会被潜移默化。看上去都不太符合,那里介绍下自家个人的做法。

第一如故会凭借于接口和服务器时间做联合,每回同步记录一个server提姆e(Unix
time),同时记录当前客户端的年华值lastSyncLocal提姆e,到今后算本地时间的时候先取curLocal提姆e,算出偏移量,再拉长server提姆e就得出时间了:

uint64_t realLocalTime = 0;
if (serverTime != 0 && lastSyncLocalTime != 0) {
    realLocalTime = serverTime + (curLocalTime - lastSyncLocalTime);
}
else {
    realLocalTime = [[NSDate date] timeIntervalSince1970]*1000;
}

如果一向没和服务器时间同步过,就不得不取当地的系统时间了,那种情景大概也没怎么震慑,表达客户端还没起来用过。

关键在于如若获得当地的大运,可以用一个小技巧来博取系统当下运作了多久,用系统的周转时刻来记录当前客户端的小时:

//get system uptime since last boot
- (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;
}

gettimeofday和sysctl都会受系统时间影响,但他俩互相做一个减法所得的值,就和序列时间毫不相关了。那样就足以避免用户修改时间了。当然用户一旦关机,过段时间再开机,会招致大家收获到的岁月慢与服务器时间,真实场景中,慢于服务器时间屡屡影响较小,大家一般担心的是客户端时间快于服务器时间。

多和服务器做时间共同,再把主要的时光校验逻辑放在Server端,就不会产出什么样意外的bug了。

总结

关于时间拍卖的逻辑就统计到此处了,关键还在于我们对于时间自己的敞亮,对于发挥时间的种种措施的领会,领会背后的法则才能选用适用的工具。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 亚洲必赢手机官网 版权所有