热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

IOS开发APP之关于时间处理详细介绍

这篇文章主要介绍了IOS开发APP之关于时间处理详细介绍的相关资料,开发APP不仅需要对API的调用还需要对时间相关的各种API之间的差别,再因场景而异去设计相应的机制,需要的朋友可以参考下

IOS 时间处理

做App避免不了要和时间打交道,关于时间的处理,里面有不少门道,远不是一行API调用,获取当前系统时间这么简单。我们需要了解与时间相关的各种API之间的差别,再因场景而异去设计相应的机制。

时间的形式

在开始深入讨论之前,我们需要确信一个前提:时间是线性的。即任意一个时刻,这个地球上只有一个绝对时间值存在,只不过因为时区或者文化的差异,处于同一时空的我们对同一时间的表述或者理解不同。这个看似简单明了的道理,是我们理解各种与时间相关的复杂概念的基石。就像UTF-8和UTF-16其实都是Unicode一样,北京的20:00和东京的21:00其实是同一个绝对的时间值。

GMT

人类对于时间的理解还很有限,但我们至少能确定一点:时间的变化是匀速的。时间前进的速度是均匀的,不会忽快忽慢,所以为了描述时间,我们也需要找到一个值,它的变化也是以均匀的速度向前变化的。

说出来你可能不信,我们人类为了寻找这个参考值,来精确描述当前的时间值,都经历了漫长岁月的探索。你可以尝试思考下,生活中有什么事物是随着时间均匀变化的,它具备的数值属性,会随着时间处于绝对的匀速变化状态。

前人发现抬头看太阳是个好办法,太阳总是按规律的“早起晚落”,而且“亘古不变”,可以用太阳在一天当中所处的位置来描述当前的时间。后来不同地区的文化需要交流,你这里太阳正高空照,我这可能已经下山了,所以需要有一个公共的大家都认可的地方,以这个地方太阳的位置来做参考着,沟通起来就会方便很多。最后选择的是英国伦敦的格林尼治天文台所在地,以格林尼治的时间作为公共时间,也就是我们所说的GMT时间(Greenwich Mean Time)。

UTC

太阳所处的位置变化跟地球的自转相关,过去人们认为地球自转的速率是恒定的,但在1960年这一认知被推翻了,人们发现地球自转的速率正变得越来越慢,而时间前进的速率还是恒定的,所以UTC不再被认为可以用来精准的描述时间了。

我们需要继续寻找一个匀速前进的值。抬头看天是我们从宏观方向去寻找答案,科技的发展让我们在微观方面取得了更深的认识,于是有聪明人根据微观粒子原子的物理属性,建立了原子钟,以这种原子钟来衡量时间的变化,原子钟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和市区和文化无关,所以要展示具体格式的时间,我们需要NSDateFormatter和NSTimeZone的辅助。

另外关于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 
- (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 

输出结果为:

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了。

总结

关于时间处理的逻辑就总结到这里了,关键还在于我们对于时间本身的理解,对于表达时间的各种方式的理解,理解背后的原理才能选择合适的工具。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!


推荐阅读
  • 我一直都有记录信息的习惯,不知是从什么时候开始,大约是在工作后不久。如今还真有点庆幸从那时开始记了点东西,当然是电子版的,写 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
  • macOS Big Sur全新设计大版本更新,10+个值得关注的新功能
    本文介绍了Apple发布的新一代操作系统macOS Big Sur,该系统采用全新的界面设计,包括图标、应用界面、程序坞和菜单栏等方面的变化。新系统还增加了通知中心、桌面小组件、强化的Safari浏览器以及隐私保护等多项功能。文章指出,macOS Big Sur的设计与iPadOS越来越接近,结合了去年iPadOS对鼠标的完善等功能。 ... [详细]
  • 本文介绍了互联网思维中的三个段子,涵盖了餐饮行业、淘品牌和创业企业的案例。通过这些案例,探讨了互联网思维的九大分类和十九条法则。其中包括雕爷牛腩餐厅的成功经验,三只松鼠淘品牌的包装策略以及一家创业企业的销售额增长情况。这些案例展示了互联网思维在不同领域的应用和成功之道。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了如何找到并终止在8080端口上运行的进程的方法,通过使用终端命令lsof -i :8080可以获取在该端口上运行的所有进程的输出,并使用kill命令终止指定进程的运行。 ... [详细]
  • 禁止程序接收鼠标事件的工具_VNC Viewer for Mac(远程桌面工具)免费版
    VNCViewerforMac是一款运行在Mac平台上的远程桌面工具,vncviewermac版可以帮助您使用Mac的键盘和鼠标来控制远程计算机,操作简 ... [详细]
  • 本文详细介绍了云服务器API接口的概念和作用,以及如何使用API接口管理云上资源和开发应用程序。通过创建实例API、调整实例配置API、关闭实例API和退还实例API等功能,可以实现云服务器的创建、配置修改和销毁等操作。对于想要学习云服务器API接口的人来说,本文提供了详细的入门指南和使用方法。如果想进一步了解相关知识或阅读更多相关文章,请关注编程笔记行业资讯频道。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
author-avatar
试管婴儿
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有