我正在寻找在 iOS 上为 SMPTE 时间码 (HH:MM:SS:FF) 创建一个倒数计时器。基本上,它只是一个分辨率为 33.33333ms 的倒数计时器。我不太确定 NSTimer 是否足够准确,可以指望触发事件来创建这个计时器。每次此计时器递增/递减时,我都想触发一个事件或调用一段代码。
我是 Objective-C 的新手,所以我正在从社区中寻找智慧。有人建议了CADisplayLink类,寻求一些专家建议。
我正在寻找在 iOS 上为 SMPTE 时间码 (HH:MM:SS:FF) 创建一个倒数计时器。基本上,它只是一个分辨率为 33.33333ms 的倒数计时器。我不太确定 NSTimer 是否足够准确,可以指望触发事件来创建这个计时器。每次此计时器递增/递减时,我都想触发一个事件或调用一段代码。
我是 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 次,这似乎是您所追求的。
请注意,这与视频帧处理有关,因此您需要非常快速地在回调中完成工作。
你基本上没有任何保证NSTimer
or dispatch_after
; 他们安排代码在主线程上触发,但如果其他事情需要很长时间才能执行并阻塞主线程,则您的计时器将不会触发。
也就是说,您可以轻松避免阻塞主线程(仅使用异步 I/O)并且事情应该会非常好。
您没有在计时器代码中确切说明您需要做什么,但是如果您需要做的只是显示倒计时,那么只要您根据系统时间而不是数字计算 SMPTE 时间就可以了根据您的计时器间隔,您认为应该经过的秒数。如果你这样做,你几乎肯定会漂移并与实际时间不同步。相反,请记下您的开始时间,然后根据该时间进行所有数学运算:
// 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.333333 毫秒后调用该代码。如果这将是一个循环排序交易,您可能希望使用该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 文档。