iOS关于时间的处理

By admin in 天文台 on 2018年10月22日

做App避免不了若与时空打交道,关于时间之处理,里面有过多门路,远不是单排API调用,获取当前系统时这样简单。我们用了解及时相关的各种API之间的反差,再因为气象而异去设计相应的建制。

岁月之花样

当起深入座谈之前,我们得确信一个前提:时间是线性的。即随意一个时刻,这个地上单独生一个决时间价值在,只不过因为时区或者文化之出入,处于同一时空之我们本着同一时间的发表还是了解不同。这个看似简单明了的理,是咱清楚各种和工夫相关的复杂性概念的内核。就比如UTF-8和UTF-16其实还是Unicode一样,北京之20:00及东京之21:00其实是和一个纯属的时光价值。

GMT

人类对于日之解还不行单薄,但我们起码会确定一点:时间之变更是匀速的。时间发展的快慢是咸匀的,不会见忽快忽慢,所以为了描述时间,我们呢欲找到一个价,它的转呢是为备匀的快向前弯的。

说出你或许不信仰,我们人类为了寻觅这参考值,来规范描述当前底流年价值,都更了漫长岁月的追究。你得品尝思考下,生活着起啊东西是乘时空全匀变化的,它装有的数值属性,会趁着时光处在绝对的匀速变化状态。

前任发现抬头看太阳是个好措施,太阳总是以常理的“早于晚落”,而且“亘古不更换”,可以就此阳光在同一龙中所处的位置来叙述当前的时。后来差地区的学识要交流,你这边太阳正高空照,我立马可能已下山了,所以要有一个国有的门阀还认同的地方,以这个地方太阳之岗位来开参考着,沟通起来就会有益于广大。最后选择的凡英国伦敦的格林尼治天文台所在地,以格林尼治的年华作集体时间,也就是咱们所说之GMT时间(Greenwich
Mean Time)。

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.

自者的叙述不难看出CFAbsoluteTimeGetCurrent()的定义以及NSDate非常相像,只不过参考点是:以GMT为专业的,2001年元月一日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年1月1声泪俱下
00:00:00啊尺度时间,当前时空相差基准点偏移的秒数。上述API返回的价是1481266031,表示手上工夫距离UTC
1970年1月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,CFAbsoluteTimeGetCurrent()一样,都是于时装备的网时影响。只不过是参考的时刻基准点不相同而已。我们与服务器通讯的上一般下Unix
time。

mach_absolute_time()

mach_absolute_time()可能就此到之校友比较少,但这个定义非常主要。

眼前提到我们得找到一个全匀变化的属于性值来讲述时间,而于咱们的iPhone上正有一个如此的价值是,就是CPU的钟周期数(ticks)。这个tick的数值可用来描述时间,而mach_absolute_time()返回的就是是CPU已经运行的tick的数。将是tick数经过一定的易就可变成秒数,或者纳秒数,这样就是跟时间一直涉及了。

然而此tick数,在每次手机还开之后,会再次开计数,而且iPhone锁屏进入休眠之后tick也会见中断计数。

mach_absolute_time()不见面让系统时影响,只为设备又开和休眠行为影响。

CACurrentMediaTime()

CACurrentMediaTime()可能接触到的同学会多片,先看下官方概念:

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

CACurrentMediaTime()就是以方面mach_absolute_time()的CPU
tick数转化成秒数的结果。以下代码:

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

返回的就是是开机后设备累计运行了(设备休眠不统计在内)多少秒,另一个API也克回来相同之价:

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

CACurrentMediaTime()也非会见叫系统时影响,只让设备再度开和休眠行为影响。

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

好望CACurrentMediaTime与NSDate代码本身的淘差异在几粗秒,而我们举行UI性能优化的维度在毫秒级别,几只微秒的歧异完全不见面影响我们最终之判定结果。所以利用NSDate做benchmark完全是卓有成效的,以下是自身常用之少数只特大:

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

场面二:客户端和服务器之间的日同步

当下也是咱们经常遇到的状况,比如电商类App到零点的时起抢购,比如商品限购倒计时等等,这种状况下用我们拿客户端的日子以及服务器保持一致,最着重之凡,要防止用户通过断网修改系统时,来震慑客户端的逻辑。

正如广泛的做法是,在有些常用之Server接口里面带齐服务器时间,每调用同赖接口,客户端就跟服务器时间召开一样不好同并记录下来,但问题是何许防范用户改也?

上面提到的NSDate,CFAbsoluteTimeGetCurrent,gettimeofday,sysctl都是尾随系统时变更的,mach_absolute_time和CACurrentMediaTime虽然是根据CPU时钟数,不为系统时影响,但于蛰伏和重启的上还是会于潜移默化。看上去还无太相符,这里介绍下自己个人的做法。

第一还是会见借助让接口及服务器时间召开同,每次同步记录一个serverTime(Unix
time),同时记录时客户端的时间值lastSyncLocalTime,到后来竟本地时间的时先取curLocalTime,算有偏移量,再添加serverTime就得发时间了:

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-2018 亚洲必赢手机官网 版权所有