iOS - NSTimer [翻译]
原文链接
定时器通过一定的时间间隔触发,向目标对象发送指定消息。
概述
定时器与 RunLoops 协同工作。RunLoops 保持对它们定时器的强引用,所以当你添加一个定时器到 RunLoop 后,不必对此定时器保持强引用。
你应该去了解 RunLoop 是如何运作的以便高效使用定时器,参见 Threading Programming Guide。
定时器并非实时机制。如果定时器的触发时间刚好处于 RunLoop 进行长时间 callout,或处于一个不对该定时器进行检测的模式,这个定时器不会触发,直到 RunLoop 下一次的检测。所以,定时器实际触发时间会比预订时间晚一些。
NSTimer 通过 toll-free 桥接的方式与 Core Foundation 中的 CFRunLoopTimerRef 对应。参见 Toll-Free Bridging。
重复和非重复定时器
你可以在创建定时器时指定该定时器是重复或是非重复。非重复定时器只触发一次而后自动失效,从而避免了定时器再次触发。与之相比,重复定时器触发后会将自身重新添加到同个 RunLoop 中。一个重复定时器总是基于计划触发时间而非实际触发时间去制定下次触发计划。举个例子,如果一个定时器计划在一个特定时间触发,然后每 5s 重复一次,计划触发时间总是与原始时间相差 5s 的倍数,即使实际触发时间发生了延迟。如果触发时间延迟了超过一个或更多周期,定时器在这段时间里只触发一次;然后定时器会被重新制定计划,等待下次的触发。
容错时间
iOS 7+ / macOS 10.9+,你可以为定时器指定一个容错时间(tolerance)。这种灵活触发的机制降低了系统能耗,提升了响应能力。定时器可能会在 [计划触发时间, 计划触发时间+容错时间] 之间的任一时刻触发。定时器在计划触发时间之前不会触发。对重复定时器来说,下次触发时间由原始时间进行计算,而不考虑容错时间以避免发生漂移。容错时间的默认值为 0,这意味着没有申请额外的容错时间。无论容错时间设置为多少,系统依然保留对某些定时器使用小额容错时间的权利。
作为用户,你可以为定时器指定一个合适的容错时间。对于重复定时器,一个通用的规则是,将容错时间设定为周期的 10% 以上。即使是小额的容错时间,也会对应用程序电量使用产生显著的积极影响。系统可能执行容错时间的最大值。
安装定时器到 RunLoop 中
一个定时器同一时间只能注册到一个 RunLoop 中,虽然它可以通过所在的 RunLoop 被添加到多个 RunLoop 模式。有三种方法去创建一个定时器:
- 使用 scheduledTimerWithTimeInterval:invocation:repeats: 或者 scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 类方法在当前的 RunLoop 中以默认模式创建一个定时器
- 使用 timerWithTimeInterval:invocation:repeats: 或者 timerWithTimeInterval:target:selector:userInfo:repeats: 类方法去创建一个定时器,但并不添加到 RunLoop 中。(创建后,你必须通过调用 addTimer:forMode: 方法手动将其添加到相应的 NSRunLoop 对象中)
- 使用定时器初始化方法 initWithFireDate:interval:target:selector:userInfo:repeats:。(创建后,你必须通过调用 addTimer:forMode: 方法手动将其添加到相应的 NSRunLoop 对象中)
定时器一旦被添加到 RunLoop 中,在失效前它都会根据指定时间间隔进行触发。一个非重复定时器会在它触发后失效。然而,对重复定时器来说,你必须通过调用 invalidate 方法去失效它。调用此方法将请求从当前 RunLoop 中移除定时器;结果是,你应该总是在同一线程中调用 invalidate 方法去移除已安装的定时器。失效的定时器会被立即禁用,使得它不再对 RunLoop 产生影响。RunLoop 会在 invalidate 返回前或更晚一些移除对定时器的引用。定时器对象一旦失效,就不能再被使用。
重复定时器触发后,它根据上次触发时间及周期的整数倍为最近的未来安排下一次触发时间,而无视容错时间。如果执行调用的时间长于指定时间间隔,则定时器仅调度下一次触发;也就是说,定时器不会视图补偿调用时可能发生的未触发事件。
其他
请勿为 NSTimer 实现子类。