更可靠和高精度的 iOS 定時器

定時器一般用于延遲一段時間執(zhí)行特定的代碼,必要的話按照指定的頻率重復(fù)執(zhí)行单寂。iOS 中延時執(zhí)行有多種方式,常用的有:

  • NSTimer
  • NSObject 的 (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
  • CADisplayLink
  • GCD 的 dispatch_after
  • GCD 的 dispatch_source_t

每種方法創(chuàng)建的定時器腻格,其可靠性與最小精度都有不同诊赊。可靠性指是否嚴(yán)格按照設(shè)定的時間間隔按時執(zhí)行,最小精度指支持的最小時間間隔是多少仅孩。

1. NSRunLoop

談到定時器托猩,首先需要了解的一個概念是 NSRunLoop。NSRunLoop 是消息處理的一種機(jī)制杠氢,類似于 Windows 中的消息循環(huán)站刑,有個更通用的叫法是 Event Loop

其原理很簡單鼻百,啟動一個循環(huán)绞旅,無限地重復(fù)接受消息->等待消息->處理消息這個過程,直到退出温艇。偽代碼如下:

void loop() {
    do {
        void *msg = getMessage();
        processMessage(msg);
    } while (msg != quit);
}

每個線程內(nèi)部都會有一個 RunLoop因悲,啟動 RunLoop 之后,就能夠讓線程在沒有消息時休眠勺爱,在有消息時被喚醒并處理消息晃琳,避免資源長期被占用。

在 iOS 中,NSThead 和 NSRunLoop 是一一對應(yīng)的卫旱,但創(chuàng)建線程的時候不會默認(rèn)創(chuàng)建 NSRunLoop人灼,實(shí)際上也不允許自己創(chuàng)建 NSRunLoop,在線程內(nèi)第一次調(diào)用[NSRunLoop currentRunLoop]的時候才會自動創(chuàng)建顾翼。

1.1 NSRunLoop 處理的輸入源(input sources):

  • 鼠標(biāo)投放、鍵盤事件。
  • NSPort 對象适贸。
  • NSConnection 對象灸芳。

NSRunLoop 也處理 NSTimer 事件,但 NSTimer 并不屬于輸入源的一種拜姿。

1.2 蘋果使用 NSRunLoop 實(shí)現(xiàn)的功能:

  • 硬件操作烙样,如觸摸、按鍵蕊肥、搖晃等谒获。
  • 手勢操作。
  • 界面刷新晴埂,如更新了 UI 的 frame究反,或手動調(diào) setNeedsLayout/setNeedsDisplay。
  • 定時器儒洛。包括 NSTimer精耐、CADisplayLink、PerformSelecter琅锻、GCD卦停。
  • 網(wǎng)絡(luò)請求。

深入了解 RunLoop有更深入完整的介紹恼蓬。

2. NSTimer

最常用惊完,能滿足對間隔要求不嚴(yán)格、對精確度不敏感的需求处硬。

2.1 使用方法

- (void)startNSTimer {
    self.timer = [NSTimer scheduledTimerWithTimeInterval:self.timeInterval target:self selector:@selector(onNSTimerTimeout:) userInfo:nil repeats:YES];
}

- (void)onNSTimerTimeout:(id)sender {
    NSLog(@"onNSTimerTimeout");
}

2.2 可靠性

不可靠小槐,其所在的 RunLoop 會定時檢測是否可以觸發(fā) NSTimer 的事件,但由于 iOS 有多個 RunLoop 的運(yùn)行模式荷辕,如果被切到另一個 run loop凿跳,NSTimer 就不會被觸發(fā)。每個 RunLoop 的循環(huán)間隔也無法保證疮方,當(dāng)某個任務(wù)耗時比較久控嗜,RunLoop 的下一個消息處理就只能順延,導(dǎo)致 NSTimer 的時間已經(jīng)到達(dá)骡显,但 Runloop 卻無法及時觸發(fā) NSTimer疆栏,導(dǎo)致該時間點(diǎn)的回調(diào)被錯過曾掂。

蘋果官方文檔:

A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. If a timer’s firing time occurs during a long callout or while the run loop is in a mode that is not monitoring the timer, the timer does not fire until the next time the run loop checks the timer.

2.3 最小精度

理論上最小精度為 0.1 毫秒。不過由于受 Runloop 的影響壁顶,會有 50 ~ 100 毫秒的誤差珠洗,所以,實(shí)際精度可以認(rèn)為是 0.1 秒若专。

蘋果官方文檔:

Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds.

2.4 實(shí)測結(jié)果

間隔 0.1 秒险污,調(diào)用12次。其中倒數(shù)第二次調(diào)用前會執(zhí)行一個比較耗時的運(yùn)算任務(wù)富岳。

代碼:

- (void)startNSTimer {
    [self setupConfig];

    [self runNSTimerIfNeeded];

    NSLog(@"NSTimer start with interval: %.3f ms, start time: %@, total count: %d", self.timeInterval * 1000, [self timeStringWithTime:self.startTime], (int)self.maxCount);
}

- (void)runNSTimerIfNeeded {
    self.timer = [NSTimer scheduledTimerWithTimeInterval:self.timeInterval
                                     target:self
                                   selector:@selector(onNSTimerTimeout:)
                                   userInfo:nil
                                    repeats:NO];

    self.startTime = [NSDate date];
}

- (void)onNSTimerTimeout:(NSTimer *)sender {
    NSLog(@"%d, %@", ++ self.curCount, [self diffTimeStringFromStart]);

    [self.timer invalidate];
    self.timer = nil;

    if (self.curCount < self.maxCount) {
        [self runNSTimerIfNeeded];
        [self runBusyTaskIfNeeded];
    }
}

結(jié)果:

2016-08-29 11:32:40.302 TimerDemo[37258:10736148] NSTimer start with interval: 100.000 ms, start time: 1472441560302.602 ms, total count: 12
2016-08-29 11:32:40.403 TimerDemo[37258:10736148] 1, interval: 101.045 ms, discrepancy: 1.045 ms
2016-08-29 11:32:40.505 TimerDemo[37258:10736148] 2, interval: 100.890 ms, discrepancy: 0.890 ms
2016-08-29 11:32:40.606 TimerDemo[37258:10736148] 3, interval: 101.087 ms, discrepancy: 1.087 ms
2016-08-29 11:32:40.707 TimerDemo[37258:10736148] 4, interval: 101.038 ms, discrepancy: 1.038 ms
2016-08-29 11:32:40.809 TimerDemo[37258:10736148] 5, interval: 101.061 ms, discrepancy: 1.061 ms
2016-08-29 11:32:40.910 TimerDemo[37258:10736148] 6, interval: 101.069 ms, discrepancy: 1.069 ms
2016-08-29 11:32:41.012 TimerDemo[37258:10736148] 7, interval: 101.031 ms, discrepancy: 1.031 ms
2016-08-29 11:32:41.113 TimerDemo[37258:10736148] 8, interval: 101.035 ms, discrepancy: 1.035 ms
2016-08-29 11:32:41.214 TimerDemo[37258:10736148] 9, interval: 100.890 ms, discrepancy: 0.890 ms
2016-08-29 11:32:41.315 TimerDemo[37258:10736148] 10, interval: 101.042 ms, discrepancy: 1.042 ms
2016-08-29 11:32:41.315 TimerDemo[37258:10736148] start busy task
2016-08-29 11:32:41.970 TimerDemo[37258:10736148] finish busy task
2016-08-29 11:32:41.970 TimerDemo[37258:10736148] 11, interval: 654.319 ms, discrepancy: 554.319 ms
2016-08-29 11:32:42.071 TimerDemo[37258:10736148] 12, interval: 100.906 ms, discrepancy: 0.906 ms

可以看到偏差在 1 ~ 2 毫秒左右。在第 10 次之后執(zhí)行了一個較耗時的任務(wù)拯腮,導(dǎo)致第 11 次比預(yù)期延遲了 0.5 秒執(zhí)行窖式。后面的回調(diào)仍然按照預(yù)設(shè)的延時執(zhí)行。

3. performSelector:withObject:afterDelay:

這是 NSObject 對 NSTimer 封裝后提供的一個比較簡單的延時方法动壤,內(nèi)部用的也是 NSTimer萝喘,所以,同上琼懊。

4. CADisplayLink

CADisplayLink 也可以用作定時器阁簸,其調(diào)用間隔與屏幕刷新頻率一致,也就是每秒 60 幀哼丈,間隔 16.67 ms启妹。與 NSTimer 類似,如果在兩次屏幕刷新之間執(zhí)行了一個比較耗時的任務(wù)醉旦,其中的某一幀就會被跳過饶米,造成 UI 卡頓。

4.1 使用方法

- (void)runCADisplayLinkTimer {
    CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onCADisplayLinkTimeout)];
    displayLink.frameInterval = 0.0167; // S
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    self.displayLink = displayLink;
}

- (void)onCADisplayLinkTimeout {
    NSLog(@"onCADisplayLinkTimeout");
}

4.2 可靠性

如果執(zhí)行的任務(wù)很耗時车胡,也會導(dǎo)致回調(diào)被錯過檬输,所以并不十分可靠。但是匈棘,假如調(diào)用者能夠確保任務(wù)能夠在最小時間間隔內(nèi)執(zhí)行完成丧慈,CADisplayLink 就比較可靠,因?yàn)槠聊坏乃⑿骂l率是固定的主卫。

4.3 最小精度

受限于每秒 60 幀的屏幕刷新頻率逃默,注定 CADisplayLink 的最小精度為 16.67 毫秒。誤差在 1 毫秒左右队秩。

另外需要注意的是笑旺,雖然 CADisplayLink 有一個屬性 frameInterval 是用于設(shè)置定時器的調(diào)用間隔,但是這個屬性會在第一次回調(diào)之后才生效馍资,對于第一次回調(diào)筒主,總是會以 1/60 的間隔來執(zhí)行的关噪。這樣會導(dǎo)致的結(jié)果是,比如你設(shè)置了每 1 秒執(zhí)行一次某個方法乌妙,但是第一次執(zhí)行的時候使兔,卻是在 16.7 毫秒之后,遠(yuǎn)遠(yuǎn)小于預(yù)設(shè)值藤韵。

4.4 實(shí)測結(jié)果

間隔 0.1 秒虐沥,調(diào)用12次。其中倒數(shù)第二次調(diào)用前會執(zhí)行一個比較耗時的運(yùn)算任務(wù)泽艘。

代碼:

- (void)startCADisplayLinkTimer {
    [self setupConfig];

    [self runCADisplayLinkTimer];

    NSLog(@"CADisplayLink start with interval: %.3f ms, start time: %@, total count: %d", self.timeInterval * 1000, [self timeStringWithTime:self.startTime], (int)self.maxCount);
}

- (void)runCADisplayLinkTimer {
    CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onCADisplayLinkTimeout)];
    NSInteger frameInterval = floor(self.timeInterval * 1000 / (1000 / 60.0));
    displayLink.frameInterval = frameInterval;

    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    self.displayLink = displayLink;

    self.startTime = [NSDate date];
}

- (void)onCADisplayLinkTimeout {
    NSLog(@"%d, %@", ++ self.curCount, [self diffTimeStringFromStart]);

    if (self.curCount < self.maxCount) {
        self.startTime = [NSDate date];
        [self runBusyTaskIfNeeded];
    } else {
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
}

結(jié)果:

2016-08-29 11:33:47.835 TimerDemo[37258:10736148] CADisplayLink start with interval: 100.000 ms, start time: 1472441627835.872 ms, total count: 12
2016-08-29 11:33:47.845 TimerDemo[37258:10736148] 1, interval: 10.061 ms, discrepancy: -89.939 ms
2016-08-29 11:33:47.946 TimerDemo[37258:10736148] 2, interval: 99.829 ms, discrepancy: -0.171 ms
2016-08-29 11:33:48.046 TimerDemo[37258:10736148] 3, interval: 99.573 ms, discrepancy: -0.427 ms
2016-08-29 11:33:48.145 TimerDemo[37258:10736148] 4, interval: 99.427 ms, discrepancy: -0.573 ms
2016-08-29 11:33:48.246 TimerDemo[37258:10736148] 5, interval: 99.801 ms, discrepancy: -0.199 ms
2016-08-29 11:33:48.346 TimerDemo[37258:10736148] 6, interval: 99.754 ms, discrepancy: -0.246 ms
2016-08-29 11:33:48.446 TimerDemo[37258:10736148] 7, interval: 99.791 ms, discrepancy: -0.209 ms
2016-08-29 11:33:48.546 TimerDemo[37258:10736148] 8, interval: 99.836 ms, discrepancy: -0.164 ms
2016-08-29 11:33:48.646 TimerDemo[37258:10736148] 9, interval: 99.840 ms, discrepancy: -0.160 ms
2016-08-29 11:33:48.746 TimerDemo[37258:10736148] 10, interval: 99.811 ms, discrepancy: -0.189 ms
2016-08-29 11:33:48.746 TimerDemo[37258:10736148] start busy task
2016-08-29 11:33:49.399 TimerDemo[37258:10736148] finish busy task
2016-08-29 11:33:49.400 TimerDemo[37258:10736148] 11, interval: 653.891 ms, discrepancy: 553.891 ms
2016-08-29 11:33:49.412 TimerDemo[37258:10736148] 12, interval: 12.566 ms, discrepancy: -87.434 ms

除了第一次回調(diào)欲险,間隔誤差比較大之外,別的回調(diào)誤差在 0.1 ~ 0.5 毫秒之間匹涮,精度比 NSTimer 要高天试。第 11 次回調(diào),受耗時任務(wù)影響然低,延時了 0.5 秒喜每。值得注意的是,第 12 次雳攘,延時再次與第一次回調(diào)一樣带兜,變成了 1/60 秒左右。

換言之吨灭,CADisplayLink 在第一次回調(diào)以及在耗時任務(wù)之后的回調(diào)刚照,精度不可控。

5. GCD dispatch_after

dispatch_after 用起來十分簡單喧兄,代碼緊湊易讀涩咖,而且可以很輕松地在別的線程分配延時任務(wù),所以使用范圍很廣泛繁莹。

5.1 使用方法

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    //handle timeout
});

5.2 可靠性

Any fire of the timer may be delayed by the system in order to improve power consumption and system performance. The upper limit to the allowable delay may be configured with the ‘leeway’ argument, the lower limit is under the control of the system.

5.3 最小精度

延時參數(shù)的單位是納秒檩互。如果有延時,則無法保證咨演。

5.4 實(shí)測結(jié)果

間隔 0.1 秒闸昨,調(diào)用12次。其中倒數(shù)第二次調(diào)用前會執(zhí)行一個比較耗時的運(yùn)算任務(wù)薄风。

代碼:

- (void)startDispatchAfterTimer {
    [self setupConfig];

    [self runDispatchAfterTimerIfNeeded];

    NSLog(@"DispatchAfterTimer start with interval: %.3f ms, start time: %@, total count: %d", self.timeInterval * 1000, [self timeStringWithTime:self.startTime], (int)self.maxCount);
}

- (void)runDispatchAfterTimerIfNeeded {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.timeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self onDispatchAfterTimeout];
    });

    self.startTime = [NSDate date];
}

- (void)onDispatchAfterTimeout {
    NSLog(@"%d, %@", ++ self.curCount, [self diffTimeStringFromStart]);
    if (self.curCount < self.maxCount) {
        [self runDispatchAfterTimerIfNeeded];
        [self runBusyTaskIfNeeded];
    }
}

結(jié)果:

2016-08-29 11:34:09.652 TimerDemo[37258:10736148] DispatchAfterTimer start with interval: 100.000 ms, start time: 1472441649652.825 ms, total count: 12
2016-08-29 11:34:09.756 TimerDemo[37258:10736148] 1, interval: 103.876 ms, discrepancy: 3.876 ms
2016-08-29 11:34:09.866 TimerDemo[37258:10736148] 2, interval: 109.686 ms, discrepancy: 9.686 ms
2016-08-29 11:34:09.976 TimerDemo[37258:10736148] 3, interval: 109.772 ms, discrepancy: 9.772 ms
2016-08-29 11:34:10.085 TimerDemo[37258:10736148] 4, interval: 108.764 ms, discrepancy: 8.764 ms
2016-08-29 11:34:10.195 TimerDemo[37258:10736148] 5, interval: 109.057 ms, discrepancy: 9.057 ms
2016-08-29 11:34:10.299 TimerDemo[37258:10736148] 6, interval: 104.544 ms, discrepancy: 4.544 ms
2016-08-29 11:34:10.408 TimerDemo[37258:10736148] 7, interval: 108.753 ms, discrepancy: 8.753 ms
2016-08-29 11:34:10.516 TimerDemo[37258:10736148] 8, interval: 107.597 ms, discrepancy: 7.597 ms
2016-08-29 11:34:10.626 TimerDemo[37258:10736148] 9, interval: 109.933 ms, discrepancy: 9.933 ms
2016-08-29 11:34:10.736 TimerDemo[37258:10736148] 10, interval: 109.791 ms, discrepancy: 9.791 ms
2016-08-29 11:34:10.736 TimerDemo[37258:10736148] start busy task
2016-08-29 11:34:11.394 TimerDemo[37258:10736148] finish busy task
2016-08-29 11:34:11.394 TimerDemo[37258:10736148] 11, interval: 657.669 ms, discrepancy: 557.669 ms
2016-08-29 11:34:11.496 TimerDemo[37258:10736148] 12, interval: 102.005 ms, discrepancy: 2.005 ms

平均誤差 9 毫秒饵较。

6. GCD dispatch_source_t

經(jīng)測試,dispatch_source_t 的最小精度和可靠性都與 diapatch_after 差不多遭赂。

6.1 實(shí)測結(jié)果

間隔 0.1 秒循诉,調(diào)用12次。其中倒數(shù)第二次調(diào)用前會執(zhí)行一個比較耗時的運(yùn)算任務(wù)撇他。

代碼:

- (void)startDispatchSourceTimer {
    [self setupConfig];

    [self runDispatchSourceTimerIfNeeded];

    NSLog(@"DispatchSourceTimer start with interval: %.3f ms, start time: %@, total count: %d", self.timeInterval * 1000, [self timeStringWithTime:self.startTime], (int)self.maxCount);
}

- (void)runDispatchSourceTimerIfNeeded {
    self.sourceTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());

    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 0);
    dispatch_source_set_timer(self.sourceTimer, start, (int64_t)(self.timeInterval * NSEC_PER_SEC), 0);

    dispatch_source_set_event_handler(self.sourceTimer, ^{
        [self onDispatchSourceTimeout];
    });

    dispatch_resume(self.sourceTimer);

    self.startTime = [NSDate date];
}

- (void)onDispatchSourceTimeout {
    NSLog(@"%d, %@", ++ self.curCount, [self diffTimeStringFromStart]);

    dispatch_cancel(self.sourceTimer);
    self.timer = nil;

    if (self.curCount < self.maxCount) {
        [self runDispatchAfterTimerIfNeeded];
        [self runBusyTaskIfNeeded];
    }
}

結(jié)果:

2016-08-29 11:34:24.088 TimerDemo[37258:10736148] DispatchSourceTimer start with interval: 100.000 ms, start time: 1472441664088.390 ms, total count: 12
2016-08-29 11:34:24.089 TimerDemo[37258:10736148] 1, interval: 1.429 ms, discrepancy: -98.571 ms
2016-08-29 11:34:24.196 TimerDemo[37258:10736148] 2, interval: 106.696 ms, discrepancy: 6.696 ms
2016-08-29 11:34:24.306 TimerDemo[37258:10736148] 3, interval: 109.500 ms, discrepancy: 9.500 ms
2016-08-29 11:34:24.416 TimerDemo[37258:10736148] 4, interval: 109.999 ms, discrepancy: 9.999 ms
2016-08-29 11:34:24.526 TimerDemo[37258:10736148] 5, interval: 109.744 ms, discrepancy: 9.744 ms
2016-08-29 11:34:24.636 TimerDemo[37258:10736148] 6, interval: 109.691 ms, discrepancy: 9.691 ms
2016-08-29 11:34:24.746 TimerDemo[37258:10736148] 7, interval: 109.767 ms, discrepancy: 9.767 ms
2016-08-29 11:34:24.856 TimerDemo[37258:10736148] 8, interval: 109.799 ms, discrepancy: 9.799 ms
2016-08-29 11:34:24.966 TimerDemo[37258:10736148] 9, interval: 109.820 ms, discrepancy: 9.820 ms
2016-08-29 11:34:25.076 TimerDemo[37258:10736148] 10, interval: 109.804 ms, discrepancy: 9.804 ms
2016-08-29 11:34:25.076 TimerDemo[37258:10736148] start busy task
2016-08-29 11:34:25.734 TimerDemo[37258:10736148] finish busy task
2016-08-29 11:34:25.734 TimerDemo[37258:10736148] 11, interval: 657.591 ms, discrepancy: 557.591 ms
2016-08-29 11:34:25.835 TimerDemo[37258:10736148] 12, interval: 101.295 ms, discrepancy: 1.295 ms

從結(jié)果看茄猫,與 diapatch_after 區(qū)別不大狈蚤。

7. 更高精度的定時器

上述的各種定時器,都受限于蘋果為了保護(hù)電池和提高性能采用的策略划纽,導(dǎo)致無法實(shí)時地執(zhí)行回調(diào)脆侮。如果你的確需要使用更高精度的定時器,官方也提供了方法勇劣,見 High Precision Timers in iOS / OS X

前面所述的定時器靖避,使用方法各有不同,但其核心代碼實(shí)際上是一樣的比默。

There are many API’s in iOS and OS X that allow waiting for a specified period of time. They may be Objective C or C, and they take different kinds of arguments, but they all end up using the same code inside the kernel.

而有別于普通定時器的高精度定時器幻捏,則是基于高優(yōu)先級的線程調(diào)度類創(chuàng)建的定時器,在沒有多線程沖突的情況下命咐,這類定時器的請求會被優(yōu)先處理粘咖。

7.1 實(shí)現(xiàn)方法

  • 把定時器所在的線程,移到高優(yōu)先級的線程調(diào)度類侈百。
  • 使用更精確的計(jì)時器API,換言之翰铡,你想要 10 秒后執(zhí)行钝域,就絕對在 10 秒后執(zhí)行,絕不提前锭魔,也不延遲例证。

7.2 如何使用

提高調(diào)度優(yōu)先級:

#include <mach/mach.h>
#include <mach/mach_time.h>
#include <pthread.h>

void move_pthread_to_realtime_scheduling_class(pthread_t pthread) {
    mach_timebase_info_data_t timebase_info;
    mach_timebase_info(&timebase_info);

    const uint64_t NANOS_PER_MSEC = 1000000ULL;
    double clock2abs = ((double)timebase_info.denom / (double)timebase_info.numer) * NANOS_PER_MSEC;

    thread_time_constraint_policy_data_t policy;
    policy.period      = 0;
    policy.computation = (uint32_t)(5 * clock2abs); // 5 ms of work
    policy.constraint  = (uint32_t)(10 * clock2abs);
    policy.preemptible = FALSE;

    int kr = thread_policy_set(pthread_mach_thread_np(pthread_self()),
                   THREAD_TIME_CONSTRAINT_POLICY,
                   (thread_policy_t)&policy,
                   THREAD_TIME_CONSTRAINT_POLICY_COUNT);
    if (kr != KERN_SUCCESS) {
        mach_error("thread_policy_set:", kr);
        exit(1);
    }
}

精確延時:

#include <mach/mach.h>
#include <mach/mach_time.h>

static const uint64_t NANOS_PER_USEC = 1000ULL;
static const uint64_t NANOS_PER_MILLISEC = 1000ULL * NANOS_PER_USEC;
static const uint64_t NANOS_PER_SEC = 1000ULL * NANOS_PER_MILLISEC;

static mach_timebase_info_data_t timebase_info;

static uint64_t abs_to_nanos(uint64_t abs) {
    return abs * timebase_info.numer  / timebase_info.denom;
}

static uint64_t nanos_to_abs(uint64_t nanos) {
    return nanos * timebase_info.denom / timebase_info.numer;
}

void example_mach_wait_until(int argc, const char * argv[]) {
    mach_timebase_info(&timebase_info);
    uint64_t time_to_wait = nanos_to_abs(10ULL * NANOS_PER_SEC);
    uint64_t now = mach_absolute_time();
    mach_wait_until(now + time_to_wait);
}

7.3 最小精度

小于 0.5 毫秒。這里有一份實(shí)現(xiàn)的代碼以及與普通定時器的對比迷捧。

8. 參考

原文地址:http://blog.lessfun.com/blog/2016/08/05/reliable-timer-in-ios/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末织咧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子漠秋,更是在濱河造成了極大的恐慌笙蒙,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庆锦,死亡現(xiàn)場離奇詭異捅位,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)搂抒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門艇搀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人求晶,你說我怎么就攤上這事焰雕。” “怎么了芳杏?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵矩屁,是天一觀的道長辟宗。 經(jīng)常有香客問我,道長档插,這世上最難降的妖魔是什么慢蜓? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮郭膛,結(jié)果婚禮上晨抡,老公的妹妹穿的比我還像新娘。我一直安慰自己则剃,他們只是感情好耘柱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著棍现,像睡著了一般调煎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上己肮,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天士袄,我揣著相機(jī)與錄音,去河邊找鬼谎僻。 笑死娄柳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的艘绍。 我是一名探鬼主播赤拒,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诱鞠!你這毒婦竟也來了挎挖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤航夺,失蹤者是張志新(化名)和其女友劉穎蕉朵,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阳掐,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡墓造,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了锚烦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片觅闽。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖涮俄,靈堂內(nèi)的尸體忽然破棺而出蛉拙,到底是詐尸還是另有隱情,我是刑警寧澤彻亲,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布孕锄,位于F島的核電站吮廉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏畸肆。R本人自食惡果不足惜宦芦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望轴脐。 院中可真熱鬧调卑,春花似錦、人聲如沸大咱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碴巾。三九已至溯捆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間厦瓢,已是汗流浹背提揍。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煮仇,地道東北人劳跃。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像欺抗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子强重,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容