如何在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工作。
你基本上没有NSTimer
或dispatch_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文档 。