5

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

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

4

3 回答 3

14

试试 CADisplayLink。它以刷新率(60 fps)触发。

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

这将每 2 帧触发一次,即每秒 30 次,这似乎是您所追求的。

请注意,这与视频帧处理有关,因此您需要非常快速地在回调中完成工作。

于 2012-05-23T11:41:16.967 回答
5

你基本上没有任何保证NSTimeror 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,您的方法将在显示更新时被调用。换句话说,尽可能快地使用它,但不会更快。如果您要显示时间,那可能就是要走的路。

于 2012-05-23T07:31:40.457 回答
2

如果你的目标是 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 文档

于 2012-05-23T07:09:03.443 回答