iOS关于时间的处理(二)—— 客户端和服务器的时间同步问题解决

版本记录

版本号 时间
V1.0 2017.08.30

前言

很多时候我们是需要服务器和客户端时间的同步的,比如说活动的倒计时,抢购等。而且做好时间同步还有一个优势就是防止客户端用户本地时间有误差导致问题。感兴趣的可以看上面几篇。
1. iOS关于时间的处理(一)—— 有关时间的基本知识

功能要求

要做到客户端和服务器端时间同步。


功能实现

1. 问题分析

我们可以按照下面的思路进行

  • 获取服务器某一时刻 A 的时间;
  • 记录获取到时刻 A 时的本地时间 B
  • 需要用到时间时,获取当前本地时间 C,当 C - B 作为时间间隔 D,则 A + D 则是当前服务器的时间。

这里要准确做到客户端时间和服务器时间一致,很关键的问题就是B和C不能受系统时间的影响。要解决这个问题,要依靠iOS的两个接口。

  • 获取设备当前时间 Now,该值受系统时间影响,用户如果修改时间,值也会随着变化。
  • 获取设备上次重启的时间 BootTime,该值受系统时间影响,用户如果修改时间,值也会随着变化。

由上面 iOS 提供的两个接口,我们可以获取本地时间 BC:设备自上次重启后运行的时间(BootTime - Now),该值与系统时间无关。利用时间差这一概念就很好的解决了时间受系统影响的这个问题了。

2. 代码实现

  • 获取当前 Unix Time
static func now() -> Int {
        var now =  timeval()
        var tz = timezone()
        gettimeofday(&now, &tz)
        return now.tv_sec
 }
  • 获取设备上次重启的 Unix Time
func boottime() -> Int {

        var mid = [CTL_KERN, KERN_BOOTTIME]
        var boottime = timeval()
        var size = MemoryLayout.size(ofValue: boottime)

        if sysctl(&mid, 2, &boottime, &size, nil, 0) != -1 {
            return boottime.tv_sec
        }
        return 0
 }
  • 进行时间校准
// 接口获取服务器时间处理
let serverTime = xxx                        // 获取到的服务器时间
let runTime0 = now() - boottime()            // 当前设备运行时间

// 需要用到时间时
let runTime1 = now() - boottime()            // 当前时刻设备运行时间
let currentTime = serverTime + runTime1     // 当前服务器时间

题外话

关于时间的使用,还有另外一个用途就是时间的测量,在做方法执行时间的benchmark的时候,我们获取时间的方法要满足两个要求,一是精读要高,而是API本身几乎不耗CPU时间。一般认为UI线程只要阻塞16.7ms就会出现掉帧现象,第一篇中提到的几种时间的方法,其中NSDate调用返回的精读是0.000004 S,也就是4微秒,CACurrentMediaTime()返回的精读也在微秒级别,精读上都符合要求。

下面看一下别人提供的示例代码

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

可以看出CACurrentMediaTimeNSDate代码本身的损耗差异在几微秒,而我们做UI性能优化的维度在毫秒级别,几个微秒的差异完全不会影响我们最后的判断结果。所以使用NSDate做benchmark完全是可行的。

参考文章

1. iOS 客户端与服务端做时间同步
2. iOS关于时间的处理

推荐阅读更多精彩内容