背景

在 iOS 开发中,凡是用到系统时间的,都要考虑一个问题:对时。有些业务是无需对时,或可以以用户时间为准的,比如动画用到的时间、一些日程类应用等。但电商相关的业务大都不能直接使用设备上的时间,而是需要跟服务器校准后的时间,例如:

  • 区间判断:一些优惠促销活动需要在 app 端判断当前是否在活动期间内。如果用户设备时间不准,会给用户错误的信息,导致投诉。
  • 倒计时:各种秒杀、限时促销、未支付订单的失效等的倒计时。如果用户设备时间不准,会带来倒计时结束后刷新页面,状态没变化的问题。可以测试一下电商大厂的 app,任意拨表之后倒计时仍是正确的。
  • 同步:如有数据同步的需求,设备时间不准会造成不能正确判断数据的新旧关系,可能会让旧数据覆盖新数据,造成数据丢失。
  • 请求时间戳:对于分页的数据,为了防止新插入的数据导致翻页时数据错乱,一个常见的解决方案是请求列表时加上时间戳的参数,后台过滤只显示时间戳之后的数据。如果用户设备表慢了,就会显示不出最新的数据,导致新发布内容在列表不出现的情况。

可以看出,对时这个需求是非常普遍的。不过实现起来并不难,在这里分享一下我们的经验。

解决方案

之所以叫解决方案,是因为这个功能不单是 app 端加几行代码,而是前后端配合完成的。大概思路如下:

  1. 后端需要做的:每一个网络请求的返回数据都要带有服务器当前时间戳
  2. app 端的网络框架在网络请求的公共回调处取出时间戳
  3. 将服务器时间与本地时间的差值缓存到本地
  4. 需要使用时间时,使用本地时间和缓存的时间差,算出相应的服务器时间

网络请求回调

服务器的时间戳可以加在 response body 里作为公共字段。在我的项目里,因为有少量 get 请求,所以放在了 response header 里。代码类似如下:

+ (void)handleSuccessResponse:(id)responseObject operation:(AFHTTPRequestOperation *)operation responseType:(Class)responseClass success:(void (^)(id))successBlock failure:(void (^)(NSError *))failureBlock {
long long timestamp = [[operation.response.allHeaderFields objectForKey:@"Response-Timestamp"] longLongValue];
[HAMDateTimeUtils updateServerTime:timestamp];
}

每次网络请求成功时更新时间差的缓存。

一个小的注意点是,处理 timestamp 最好始终用 long long 类型。因为 timestamp 传统上是以毫秒为单位的(虽然在 iOS 这个奇葩系统里 NSTimeInteval 是以秒为单位),在 32 位系统上 long 和 NSInteger 都存不下,会溢出。当然,现在 32 位系统的设备已经不常见了。

时间差的缓存

在更新缓存时,把服务器时间与本地当前的时间差保存在单例里。

HAMDateTimeUtils.m
- (void)updateServerTime:(long long)timestamp {
NSTimeInterval timeInteval = timestamp / 1000.0 - [[NSDate date] timeIntervalSince1970];
[self sharedInstance].timeIntevalDifference = timeInteval;
}

提供校准过的时间

需要使用时间时,根据当前时间和缓存过的时间差,计算校准后的时间:

HAMDateTimeUtils.m
+ (NSDate*)currentTime {
NSDate* serverDate = [NSDate dateWithTimeIntervalSinceNow:[self sharedInstance].timeIntevalDifference];
return serverDate;
} // 以毫秒为单位
+ (long long)currentTimeStamp {
NSTimeInterval localTime = [[NSDate date] timeIntervalSince1970];
NSTimeInterval timeDifference = [WNYDateTimeUtils sharedInstance].timeIntevalDifference; return (long long)((localTimeStamp + timeDifference) * 1000);
}

使用时只需调用 [HAMDateTimeUtils currentTime] 或 [HAMDateTimeUtils currentTimeStamp] 即可。

讨论

  • Q:这样得出的时间准确吗?
    A:会有一定误差。原因在于,服务器返回的时间戳是从服务器开始返回数据的时间,到客户端接收时会有一点延迟。不过对于我们的后台,这个延迟一般 <100 ms,对于我们的业务来说没什么影响。
    如果对准确性要求更高,可以考虑使用专门的对时接口,不知道国家天文台有没有……
    另外,这种对时的方案只是用于优化 UI 层面的显示,不能防止用户恶意的篡改。要始终记住客户端的时间戳是不可信的,后端业务凡是使用时间都务必用服务器的时间。

  • Q:缓存的时候,为什么只存在单例里,不持久化存储?
    A:这个我也考虑过,主要是觉得再次启动的时候,时间差可能会发生变化,感觉持久化没有太大的必要。如果觉得有必要的话,也可以在 userDefault 里存一份,启动时取出来即可。

iOS 时间校准解决方案的更多相关文章

  1. iOS时间问题

    在iOS开发中,经常会遇到各种各样的时间问题,8小时时差,时间戳,求时间间隔,农历等等.解决办法网上比比皆是,但大多零零散散,很多资料并没有说明其中问题.这里集中总结一下,以便于以后查阅和供大家参考. ...

  2. [控件]unigui移动端下Unidatepicker时间显示解决方案

    [控件]unigui移动端下Unidatepicker时间显示解决方案 http://tz10000.com/kong-jian-unigui-yi-dong-duan-xia-unidatepick ...

  3. Quartz定时任务和IIS程序池闲置超时时间冲突解决方案

    一.问题描述 Bs项目中用Quartz功能执行一个定时任务(每隔5分钟执行一个Job),正常情况,Quartz定时任务会5分钟执行一次,但IIS程序池闲置 超时默认为20分钟,造成的结果是:定时任务只 ...

  4. 支持WEB、Android、IOS的地图解决方案

    转自原文 支持WEB.Android.IOS的地图解决方案 工具链 GIS工具集 OpenGeo Suite 包含PostGIS, GeoServer, GeoWebCache, OpenLayers ...

  5. ios archives 出现的是other items而不是iOS Apps的解决方案

    ios archives 出现的是other items而不是iOS Apps的解决方案 项目打包时出现的是不是出现在iOS Apps栏目下面,而是Other Items而且右边对应的Upload t ...

  6. iOS时间那点事儿–NSTimeZone

    NSTimeZone **时区是一个地理名字,是为了克服各个地区或国家之间在使用时间上的混乱. 基本概念: GMT 0:00 格林威治标准时间; UTC +00:00 校准的全球时间; CCD +08 ...

  7. iOS 时间处理(转)

    NSDate NSDate对象用来表示一个具体的时间点. NSDate是一个类簇,我们所使用的NSDate对象,都是NSDate的私有子类的实体. NSDate存储的是GMT时间,使用的时候会根据 当 ...

  8. 【ntp时间校准配置】

    Ntp(网络时间协议)是一种可以通过TCP/IP网络传播,其架构模式可分为C/S(客户端/服务器),PTP(对等),broatcast(广播), mutilbrocast(组播),无论在任何系统或设备 ...

  9. iOS 循环引用解决方案

    一.BLOCK 循环引用 一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身.构成循环引用. // 定义 block 的时候,会对外部变量做一次 cop ...

随机推荐

  1. unity3d 加载优化建议 总结 from 侑虎科技

    第一部分 我们对于纹理资源的加载建议如下: 1.严格控制RGBA32和ARGB32纹理的使用,在保证视觉效果的前提下,尽可能采用“够用就好”的原则,降低纹理资源的分辨率,以及使用硬件支持的纹理格式. ...

  2. 【BZOJ】2815: [ZJOI2012]灾难

    简要题意: 给一个有向无环图,问每个节点删掉之后会导致多少个点不可达. 似乎以前拿来考过.... 我们定义一棵树,它满足对应点造成的灭绝值即为当点的子树大小-1 按照被捕食者--->捕食者的关系 ...

  3. ssh连接服务器

    1.命令行操作 第一步输入  :ssh 用户名@服务器外网ip 第二步:输入密码,回车 看到welcome提示信息即为登陆成功 输入:exit 退出 2.客户端操作 windows下载ssh软件,安装 ...

  4. HAProxy用法详解

    一.HAProxy简介 (1)HAProxy 是一款提供高可用性.负载均衡以及基于TCP(第四层)和HTTP(第七层)应用的代理软件,支持虚拟主机,它是免费.快速并且可靠的一种解决方案. HAProx ...

  5. 【转】VC 利用DLL共享区间在进程间共享数据及进程间广播消息

    1.http://blog.csdn.net/morewindows/article/details/6702342 在进程间共享数据有很多种方法,剪贴板,映射文件等都可以实现,这里介绍用DLL的共享 ...

  6. java获取当前项目或类路径

    // 获取当前项目的目录 File directory = new File("");// 参数为空 String courseFile = directory.getCanoni ...

  7. bat 命令 常用配置及其用法

    1.初衷: bat 批处理文件:当我懒得一个个操作的时候,可以把若干东西放到一个文件里面,开机运行或者需要的时候手动运行.节省时间. 2.命令集说明 2.1 常用命令 2.1.0 help 命令 /? ...

  8. Lua和C++交互 学习记录之三:全局值交互

    主要内容转载自:子龙山人博客(强烈建议去子龙山人博客完全学习一遍) 部分内容查阅自:<Lua 5.3  参考手册>中文版 译者 云风 制作 Kavcc vs2013+lua-5.3.3 1 ...

  9. Semana i 2018

    Semana i 2018 A Giga-Kilo-Gigabyte 思路: dp水题 代码: #pragma GCC optimize(2) #pragma GCC optimize(3) #pra ...

  10. Java使用Spring初识

    1.首先是引用了,然后pom.xml如下: <dependency> <groupId>org.springframework</groupId> <arti ...