如何在Objective-C / iOS中创build一个精确的计时器事件?

我期待在iOS上为SMPTE时间码(HH:MM:SS:FF)创build一个倒数计时器。 基本上,这只是一个分辨率为33.33333ms的倒数计时器。 我不太确定NSTimer是否足够准确,可以指望触发事件来创build此计时器。 每次这个计时器增加/减less时,我想发起一个事件或者调用一段代码。

我是Objective-C的新手,所以我正在从社区寻求智慧。 有人提出了CADisplayLink类,寻找一些专家的意见。

试试CADisplayLink。 它刷新率(60 fps)。

CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timerFired:)]; displayLink.frameInterval = 2; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 

这会每2帧触发一次,这是每秒30次,这似乎是你以后的事情。

请注意,这与video帧处理有关,所以您需要尽快完成callback工作。

你基本上没有NSTimerdispatch_after保证; 他们计划在主线程上触发代码,但如果其他事情需要很长时间来执行并阻止主线程,则计时器将不会触发。

也就是说,你可以很容易地避免阻塞主线程(只使用asynchronousI / O),事情应该是相当不错的。

你并没有确切地说明在定时器代码中你需要做什么,但是如果你只需要显示一个倒计时,只要你根据系统时间来计算SMPTE时间,你应该没问题,根据您的计时器间隔,您认为应该经过的秒数。 如果你这样做,你几乎肯定会漂移,并与实际时间不同步。 相反,注意你的开始时间,然后做所有的math基础:

 // Setup timerStartDate = [[NSDate alloc] init]; [NSTimer scheduledTimer... - (void)timerDidFire:(NSTimer *)timer { NSTImeInterval elapsed = [timerStartDate timeIntervalSinceNow]; NSString *smtpeCode = [self formatSMTPEFromMilliseconds:elapsed]; self.label.text = smtpeCode; } 

现在无论定时器被触发的频率如何,您都将显示正确的时间码。 (如果定时器没有足够频繁的启动,定时器将不会更新,但是当定时器更新的时候是准确的,它永远不会失去同步)。

如果您使用CADisplayLink,您的方法将被调用与显示更新一样快。 换句话说,尽可能快,但不会更快。 如果你显示的时间,这可能是一个路要走。

如果您的目标是iOS 4+,则可以使用Grand Central Dispatch:

 // Set the time, '33333333' nanoseconds in the future (33.333333ms) dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 33333333); // Schedule our code to run dispatch_after(time, dispatch_get_main_queue(), ^{ // your code to run here... }); 

这将在33.333333ms之后调用该代码。 如果这将是一个循环sorta交易,你可能想使用dispatch_after_f函数,而不是一个块使用函数指针:

 void DoWork(void *context); void ScheduleWork() { // Set the time, '33333333' nanoseconds in the future (33.333333ms) dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 33333333); // Schedule our 'DoWork' function to run // Here I pass in NULL for the 'context', whatever you set that to will // get passed to the DoWork function dispatch_after_f(time, dispatch_get_main_queue(), NULL, &DoWork); } void DoWork(void *context) { // ... // Do your work here, updating an on screen counter or something // ... // Schedule our DoWork function again, maybe add an if statement // so it eventually stops ScheduleWork(); } 

然后调用ScheduleWork(); 当你想启动计时器。 对于一个重复的循环,我个人认为这比上面的块方法稍微干净一点,但对于一次性任务,我绝对更喜欢块方法。

有关更多信息,请参阅Grand Central Dispatch文档 。