【问题标题】:Accuracy of NSTimerNSTimer 的准确性
【发布时间】:2023-04-02 09:38:01
【问题描述】:

我正在尝试使用 NSTimer 创建一个秒表样式的计时器,它每 0.1 秒递增一次,但有时它似乎运行得太快了..

这就是我的做法:

Timer =[NSTimer scheduledTimerWithTimeInterval: 0.1 target:self selector:@selector(updateTimeLabel) userInfo:nil repeats: YES];

然后:

-(void)updateTimeLabel
{
    maxTime=maxTime+0.1;
    timerLabel.text =[NSString stringWithFormat:@"%.1f Seconds",maxTime];
}

这将在Label中显示计时器的值,我以后可以利用maxTime作为计时器停止的时间......

问题是它运行非常不准确。

有没有一种方法可以确保 NSTimer 准确地每 0.1 秒触发一次?我知道 NSTimer 不准确,我要求进行调整以使其准确。

谢谢

【问题讨论】:

  • 用什么方法创建定时器?
  • 如果您正在计时事件,您应该在启动计时器时调用CFAbsoluteTimeGetCurrent(),然后在间隔结束时再次调用它。减去 2 个值,您将得到以秒为单位的经过时间。
  • 即实际经过的时间与您显示的时间无关。因此您可以大约每 0.1 秒更新一次显示,但您的更新是否稍微关闭并不重要。然后当你的计时器暂停时,使用我上次评论中的方法再次更新显示。
  • 另外,如果您希望显示器至少每 0.1 秒更新一次,我会将计时器设置为每 0.05 秒更新一次。
  • Rob - 在 *.h 文件中创建计时器,并使用 viewdidLoad 进行初始化

标签: objective-c nstimer


【解决方案1】:

根据NSTimer 文档,它并不准确。

由于典型的运行循环管理的各种输入源,计时器的时间间隔的有效分辨率被限制在 50-100 毫秒的数量级。如果计时器的触发时间发生在长时间调用期间或运行循环处于不监视计时器的模式下,则计时器不会触发,直到运行循环下次检查计时器。因此,计时器触发的实际时间可能是计划触发时间之后的很长一段时间。

您可能想要使用 GCD 中的 dispatch_after 函数,official documentation 建议使用该函数来实现此目的(创建计时器)。

如果要在指定的时间间隔后执行一次阻塞,可以使用dispatch_afterdispatch_after_f函数。


顺便说一句,我同意 Caleb 的回答。如果你不像现在那样积累错误,你可能会解决你的问题。 如果您存储开始日期并在每次迭代时使用 -timeIntervalSince: 方法重新计算时间,那么无论计时器精度如何,您最终都会得到准确的 UI 更新。

【讨论】:

  • Gabriele - 我不需要 'Time-since' ,因为我需要在标签中连续向用户显示时间......
  • 您需要它以避免累积错误。如果您在每次“迭代”时重新计算时间,则不会累积错误。否则,您将总结 NSTimer 的每一个不精确性,最终得到一个非常不精确的度量。
  • 如果你使用重复,NSTimer 并不是“不精确的”。
  • @HotLicks:您对此声明有任何参考吗?为什么重复选项会否定错误累积?
【解决方案2】:
maxTime=maxTime+0.1;

这是错误的方法。您不想使用计时器来累积经过的时间,因为您将随之累积错误。使用计时器定期触发使用NSDate 计算经过时间的方法,然后更新显示。所以,改变你的代码来做一些事情:

maxTime = [[NSDate date] timeIntervalSince:startDate];

【讨论】:

  • 但我假设 NSTimer 触发器是每 0.1 秒发生一次的事情,并且它正在增加 maxTime - maxTime 跟随计时器并且没有设置它
  • 我明白,但计时器可能会以某种方式关闭 50 毫秒。对于很多事情来说,这不是一个大错误,但它会很快加起来。没有人关心您是否每 0.1 秒准确更新显示,但是当您确实开始更新显示时,它应该尽可能准确。您可以通过基于当前时间和固定开始时间之间的差异进行计算来实现这一点,从而避免误差的累积。
【解决方案3】:

这是一个你可以用来做你想做的事的课程:

@interface StopWatch()
@property ( nonatomic, strong ) NSTimer * displayTimer ;
@property ( nonatomic ) CFAbsoluteTime startTime ;
@end

@implementation StopWatch

-(void)dealloc
{
    [ self.displayTimer invalidate ] ;
}

-(void)startTimer
{
    self.startTime = CFAbsoluteTimeGetCurrent() ;
    self.displayTimer = [ NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector( timerFired: ) userInfo:nil repeats:YES ] ;
}

-(void)stopTimer
{
    [ self.displayTimer invalidate ] ;
    self.displayTimer = nil ;

    CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ;
    [ self updateDisplay:elapsedTime ] ;
}

-(void)timerFired:(NSTimer*)timer
{
    CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ;
    [ self updateDisplay:elapsedTime ] ;
}

-(void)updateDisplay:(CFAbsoluteTime)elapsedTime
{
    // update your label here
}

@end

重点是:

  1. 通过将秒表启动时的系统时间保存到变量中来进行计时。
  2. 当秒表停止时,通过从当前时间中减去秒表开始时间来计算经过的时间
  3. 使用您的计时器更新您的显示。您的计时器是否准确并不重要。如果您试图保证显示至少每 0.1 秒更新一次,您可以尝试将计时器间隔设置为最短更新时间 (0.05 秒) 的 1/2。

【讨论】:

  • 嗨 Niels ...似乎可以工作,但标签显示的值非常大,即使我使用了经过时间的相对值...知道为什么吗?
  • +1 但有几点想法: 1. 你不能在dealloc 中使用invalidate 定时器,因为定时器的强引用会阻止dealloc 被调用。你在这里有一个强大的参考周期。您可能应该在viewWillAppearviewWillDisappear 中的invalidate 中创建计时器。 2. 我可能还建议使用CADisplayLink 而不是NSTimer,但想法是一样的。
  • 换句话说(据我所知)......我真的不需要将'elapsedTime'传递给显示方法,因为它现在被准确地触发了?我最初的 updateTimeLabel 仍然可以工作,因为使用 timerFired 中的绝对差异更新很好
  • @user2308343 我认为 nielsbot 回答的关键信息是您不应该维护自己的计数器。您不应该依赖计时器的精度。您应该以足够高的频率调用计时器以获得所需的用户体验,但使用开始 CFAbsoluteTimeGetCurrent() 和当前值之间的差异来计算经过的时间。
  • @Rob 所说的。还有@Rob:我写了一个简单的程序来测试它并调用-dealloc。我认为使用CADisplayLink() 可能会过度占用 CPU,但您会获得更准确的屏幕显示,对吧?
【解决方案4】:

不保证 NSTimer 是准确的,尽管在实践中它通常是准确的(如果你没有在你的主线程上做任何其他事情......)。但是,更新显示是完全合理的……只是不要使用回调来计算你的计时器。启动计时器时保存当前时间,并在每次启动计时器时获取现在和启动时间之间的差异。那么 NSTimer 触发的准确度并不重要,它只会影响屏幕显示更新每秒的次数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-21
    • 2021-05-10
    • 2016-06-03
    • 2019-10-09
    • 2020-06-18
    相关资源
    最近更新 更多