iOS倒计时设计思路和一个系统时间的坑

个人知识点记录,仅供参考

1.用GCD定时器更准确
2.当前时间要用服务器时间
3.如何考虑手机锁屏线程休眠
4.如何做到tableViewCell里面放倒计时
5.到期时间不变,当前时间在变,主要操作的是这个差值


Demo分析

1.创建GCD定时器 Demo用NSDate来模拟服务器当前时间

@property (nonatomic,strong) dispatch_source_t timer;
- (void)refreshDataForCountDown{
    if (!self.timer) {
        __weak typeof(self) weakSelf = self;
        self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
        dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1.0f * NSEC_PER_SEC, 0.0f * NSEC_PER_SEC);
        dispatch_source_set_event_handler(self.timer, ^{

            @autoreleasepool {
                NSLog(@"定期器跑起来了。。。");
                NSTimeInterval timerTop = ([[NSProcessInfo processInfo] systemUptime] - weakSelf.appStartTimeTop);
                NSTimeInterval timerBottom = ([weakSelf uptime] - weakSelf.appStartTimeBottom);


                weakSelf.topLabel.text = [weakSelf.formatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:weakSelf.serverTimeTop + timerTop]];
                weakSelf.bottomLabel.text = [weakSelf.formatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:weakSelf.serverTimeBottom + timerBottom]];


            }
        });
        dispatch_resume(self.timer);
    }
}





2.获取当前设备系统/进程的时间

进程时间

[[NSProcessInfo processInfo] systemUptime]

系统内核时间(这里有介绍该方法)

int sysctl(int *, u_int, void *, size_t *, void *, size_t);



这里先针对Demo分析下,这两个获取当前系统时间的方法,主要用他们来给服务器时间补差值,比如说你获取到服务器时间之后,啥都没做,过了几秒钟,你肯定需要记录系统时间,把服务器时间加上这个差值,就是时时模拟出来的服务器时间,OK这个没问题了,那么问题来了,当你手机开着的时候,没问题的,你看不出来的,但是你锁屏你试试,等几分钟来看看,你会发现NSProcessInfo对应下的时间慢了。。。。。
直接看这个吧,这里有详细讨论
这里写图片描述
但是有个注意点,我也被恶心了一下午,测试的时候记得把你的数据线拔了,不然你锁屏的时候进程也不会休眠的。。



看下效果,记住你拔掉你的数据线,锁屏,等几分钟来看看,两个时间就不准了
iOS倒计时设计思路和一个系统时间的坑_第1张图片

总结:NSProcessInfo是不准确的,我们要用内核的系统时间,这样才不受线程休眠的影响,你无论什么状态下获取到都是正确的时间差,加上你请求到的服务器时间,就是完美的当前时间

- (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;
}



3.重新刷新一次时间就恢复了
iOS倒计时设计思路和一个系统时间的坑_第2张图片

- (IBAction)refreshTime:(id)sender {
    self.serverTimeTop = [[NSDate date] timeIntervalSince1970];
    self.serverTimeBottom = [[NSDate date] timeIntervalSince1970];

    self.appStartTimeTop = [[NSProcessInfo processInfo] systemUptime];
    self.appStartTimeBottom = [self uptime];
}



如果你不刷新,你获取到的systemUptime已经滞后了(线程休眠导致的),那么你算出来的差值自然已经滞后了,但是内核的时间是肯定准确的,如果你刷新一次,又同步了两个时间,又恢复了,这个Demo只是验证NSProcessInfo不能用来进行获取进程行走的时间,稍微分析下倒计时逻辑

倒计时思路 —>>有问题的话可以留言交流


1.核心思想是只操作数据源,KVO的方式进行UI倒计时
2.通过请求数据获取到一个到期时间,然后根据系统时间算于出当前时间的差值
3.在适当的地方开启定时器修改数据源的差值字段,cell进行监听刷新UI
4.手机锁屏的时候切换到后台,再次进来你要再次根据当前服务器时间刷新这个差值,重新进行当前的倒计时,如果不刷新,线程休眠,你的差值是不变的,而系统时间在走,因此都要跟着变,就在App进入前台的时候重新给数据源的差值进行计算赋值(这里如何保证系统时间准确,记得用sysctl内核方法去获取)

注意:在确保获取系统时间准确的情况下,用sysctl,每次请求都需要保存两个值,一个是服务器时间,json返回的,另一个就是这次请求的系统时间。为什么呢?因为你倒计时不是每次都会重新请求数据,而且即使你请求数据,你各种开销到你正真倒计时的时候,服务器时间没变啊,因为你获取到的服务器时间是早先请求的时间,这个时候,你刚才保存的系统时间就有用了,你在每次回去服务器时间的时候,都需要再重新调用sysstl方法获取最新的系统内核时间,减去你当时请求回来记录的那个字段时间,差值就是这段时间系统走过的差值,然后加上你的原先服务器时间,才是你当前的服务器时间。然后才能拿这个服务器时间和倒计时截止时间,结算出最终用来跑定时器任务的差值,这个值才是最后倒计时显示用的

你可能感兴趣的:(基础知识)