iOS 时间校准

2022-06-15  本文已影响0人  _keepGoingKeepF

今天接到一个很有意思的需求。消息需要同步,需要时间校准,防止用户恶意篡改时间或无网络情况下定位准确时间。
在网上找了下资料,很多使用了ios-ntp,个人使用后感觉并不好用。

研究了一大堆,最后确定了方案。感谢大佬提供的思路参考:https://zhuanlan.zhihu.com/p/24377367

首先是获取设备运行时间:

+ (NSTimeInterval)runTime{
    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;
}

然后是获取服务器时间的方法:

- (NSDate *)getInternetDate{
    NSArray *urlList = @[@"http://www.baidu.com",
                         @"https://www.sogou.com",
                         @"https://www.sina.com.cn"];
    NSDate *localeDate;
    for (NSString *urlString in urlList) {
        NSString *url = [urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
        [request setURL:[NSURL URLWithString: url]];
        [request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
        [request setTimeoutInterval: 2];
        [request setHTTPShouldHandleCookies:FALSE];
        [request setHTTPMethod:@"GET"];
         NSHTTPURLResponse *response;
        [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
        NSString *date = [[response allHeaderFields] objectForKey:@"Date"];
        date = [date substringFromIndex:5];
        date = [date substringToIndex:[date length]-4];
        NSDateFormatter *dMatter = [[NSDateFormatter alloc] init];
        dMatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
        [dMatter setDateFormat:@"dd MMM yyyy HH:mm:ss"];
        NSDate *netDate = [[dMatter dateFromString:date] dateByAddingTimeInterval:0];
       NSTimeZone *zone = [NSTimeZone systemTimeZone];
       NSInteger interval = [zone secondsFromGMTForDate: netDate];
       localeDate = [netDate  dateByAddingTimeInterval: interval];
        if (localeDate != nil) break;
    }
    
    if (localeDate) {
        [[NSUserDefaults standardUserDefaults]setObject:localeDate forKey:CX_ST];
        [[NSUserDefaults standardUserDefaults]synchronize];
    }
    return localeDate;
}

说明一下原理:这里当我们进入APP时先需要获取一次服务器时间和系统运行时间,当我们需要使用准确时间的时候再去请求一下运行时间。这样用第一次请求的服务器时间+两次运行时间的差值就是现在准确的时间。可以做到无视本地时间的修改。我把计算方法整合了一下:

+ (NSTimeInterval)getRightTime{
    
    NSDate *lastServerDate = [[NSUserDefaults standardUserDefaults]objectForKey:CX_ST];
    if (!lastServerDate) {
        /*第一次拉取服务器时间*/
        NSTimeInterval nowTime =  [[self getInternetDate]timeIntervalSince1970];//获取新服务器时间并保存
        NSTimeInterval runTime =  [NSObject runTime];
        [self saveRunTime:runTime];//保存设备运行时间
        return nowTime;
    }else{
        /*第二次已有服务器时间*/
        NSString *lastRunTime = [[NSUserDefaults standardUserDefaults]objectForKey:CX_RT];//获取上次设备运行时间
        NSTimeInterval lastTime = [lastRunTime doubleValue];
        NSTimeInterval nowRunTime = [NSObject runTime];//获取当前设备运行时间并保存
        NSTimeInterval lastServerTimeIv= [lastServerDate timeIntervalSince1970];
        return lastServerTimeIv + (nowRunTime-lastTime);//返回上次服务器时间和两次设备运行时间差值的和
    }
}

这里还需要注意一个问题。。。

就是当设备重启以后系统运行时间是会重置的,这时候去做计算的话就会出问题。
这里也提供一个方法:
就是当我们离开APP时记录一下系统运行时间time1。 applicationDidEnterBackground:
当我们回来时又获取一次运行时间time2,然后比对时间。
如果系统未重启,那么time2是一定>time1的
由此可以判断我们是否需要重置一下保存在本地的运行时间和是否需要再向服务器拉取一次准确时间。

-(void)applicationDidEnterBackground:(UIApplication *)application{
    NSTimeInterval backTime = [NSObject runTime];
    NSString *backTimeString = [NSString stringWithFormat:@"%f",backTime];
    [[NSUserDefaults standardUserDefaults]setObject:backTimeString forKey:@"backTime"];
    [[NSUserDefaults standardUserDefaults]synchronize];
}

- (void)applicationWillEnterForeground:(UIApplication *)application{
    /*获取系统运行时间 如果大于之前亚后台保存的系统运行时间 则不重置,反之,则认为设备重启,重置 */
    NSString *lastRunTime = [[NSUserDefaults standardUserDefaults]objectForKey:@"backTime"];//获取上次设备运行时间
    NSTimeInterval lastTime = [lastRunTime doubleValue];
    NSTimeInterval enterTime = [NSObject runTime];
    if (enterTime < lastTime) {
        [NSObject resetTimeCount];
    }
}

如果是全程无网乱改时间的情况,例如IM,目前的想法是可以对照之前的已经发送的消息进行一个模糊时间判定,最后以实际发送成功的结果update数据。

好了,本文至此结束。

—————————————————————————————————————————————

上一篇下一篇

猜你喜欢

热点阅读