当 runloop 被阻塞时,NSTimer 不会触发

Posted

技术标签:

【中文标题】当 runloop 被阻塞时,NSTimer 不会触发【英文标题】:NSTimer not firing when runloop is blocked 【发布时间】:2011-01-01 00:51:55 【问题描述】:

我的应用程序即将完成,Beta 测试在秒表部分发现了一个错误... 秒表使用 nstimer 进行计数,并有一个用于存储圈数的表,但是当圈数表滚动时,手表会停止或暂停,并且不会弥补丢失的时间。

通过以下方式消除了这种停滞:

startingTime = [[NSDate date] timeIntervalSince1970];

计算经过的时间。

但我仍然使用 NSTimer 每 0.1 秒触发一次,这意味着即使经过的时间最终会正确更新,滚动仍然会暂停计时器......并将其与 Apple 秒表进行比较,这让我想知道那个秒表是否有一个单独的线程来计算经过的时间。有谁知道这是怎么做的吗?

现在,使用 Epoch 以来的时间在某种意义上运行良好,但它使启动、停止和重新启动秒表的事情变得复杂

当手表停止时,时间会被存储并用于计算手表重新启动时的偏移量,但似乎会引入一些延迟,并且在手表重新启动时时间会明显提前。

对于根本原因或解决方案的任何想法将不胜感激。

【问题讨论】:

【参考方案1】:

bbum 的回答提供了一种更好的方式来设计您的应用程序,但是如果您希望无论用户是否在操作 UI,您的计时器都会触发,您需要将其添加到运行循环的跟踪模式中。

假设您正在为 iPhone 开发,该模式为UITrackingRunLoopMode。如果您正在为 Mac 开发,则有一个类似名称的 NSEventTrackingRunLoopMode

NSRunLoop *runloop = [NSRunLoop currentRunLoop];
NSTimer *timer = [NSTimer timerWithTimeInterval:0.1 target:self selector:@selector(myTimerAction:) userInfo:nil repeats:YES];
[runloop addTimer:timer forMode:NSRunLoopCommonModes];
[runloop addTimer:timer forMode:UITrackingRunLoopMode];

【讨论】:

这仍然不能保证整体计时的准确性。定时器不能保证完全按照其配置的时间间隔触发。更糟糕的是,如果每次计时器触发都增加了整个间隔,那么每次计时器触发的错误量都会增加! 我并不打算争辩说他们会保证在控件跟踪时除了计时器之外的任何东西都会触发。正如您在回答中所说,在单圈开始时存储日期并使用它来计算经过的时间要准确得多(并且更简单的 IMO)。 +1 为我工作。 bbum 的回答对我来说没有任何意义。 我想知道是否有任何警告允许在跟踪期间触发选择器。例如这段时间应该避免 UI 布局吗?我想这并不像从不同的线程调用那样危险...... 有很好的理由不将计时器赋予 UI 跟踪的优先级。显然——UI 跟踪会受到影响。滚动、缩放、平移和其他连续的触摸手势不会像预期的那样流畅。如果您只想在计时器触发时记录时间或做一些非常轻量级的事情,那没关系。计时器在与 UI 跟踪事件相同的线程和 runloop 上触发,因此操作 UI 是安全的 --- 但正如我之前提到的,这是一个非常糟糕的主意。它只是停止 UI 跟踪。【参考方案2】:

如果事件循环没有运行,任何计时器都不会触发,直到事件循环可以再次运行。即使事件循环没有被阻塞,定时器也不能保证完全按照其配置的时间间隔触发。如果您的计时完全基于计时器触发,则错误数量会随着时间的推移而增加。

您需要与计时器的触发分开跟踪持续时间。每次触发计时器时,重新计算您的持续时间并重新显示。

对于启动/暂停/重启/停止类型的设置,您通常希望:

在启动时获取时间(作为 NSDate 实例或作为 NSTimeInterval 值)

暂停或停止时,获取暂停/停止时的时间。从这个时间减去开始时间,你就有了间隔的持续时间

重启时,获取重启时的时间但也要保持已经过去的持续时间

在暂停/停止时,获取暂停/停止的时间并添加已经过去的持续时间

一般来说,使用 NSTimeInterval 值(只是双精度值)来完成所有这些操作是最简单的。但是,如果您需要跟踪事件发生的实际时刻,请改用 NSDate 实例。

【讨论】:

以上是关于当 runloop 被阻塞时,NSTimer 不会触发的主要内容,如果未能解决你的问题,请参考以下文章

杨小麦OC之旅--RunLoop&&NSTimer

NStimer 被堵塞

iOS中RunLoop和线程的关系

NSTimer和Runloop的关系

在子线程中使用runloop,正确操作NSTimer计时的注意点 三种可选方法

主线程 Runloop 在打开 nsmenu 时被阻塞