iOS倒數計時設計思路和一個系統時間的坑

Deft_MKJing宓珂璟發表於2017-05-08

個人知識點記錄,僅供參考

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倒數計時設計思路和一個系統時間的坑

總結: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倒數計時設計思路和一個系統時間的坑

- (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方法獲取最新的系統核心時間,減去你當時請求回來記錄的那個欄位時間,差值就是這段時間系統走過的差值,然後加上你的原先伺服器時間,才是你當前的伺服器時間。然後才能拿這個伺服器時間和倒數計時截止時間,結算出最終用來跑定時器任務的差值,這個值才是最後倒數計時顯示用的

相關文章