Timer 表示一个定时器,在经过一定的时间间隔后触发,向目标对象发送指定的消息。
1 | class Timer : NSObject |
Timer
与run loop
一起工作,run loop
维护对其Timer
的强引用,因此在将Timer
添加到run loop
后,你不必维护自己对Timer
的强引用。Timer
不是实时机制。如果Timer
的触发时间发生在长时间run loop
调用期间 或者run loop
处于不监视timer
的mode
时,则直到run loop
下次检查timer
时,Timer
才会启动。因此,Timer
触发的实际时间可能要晚得多。Timer
和Core Foundation
的CFRunLoopTimer
是桥接的。不要子类化
Timer
可以指定timer
在创建时是repeating
还是 nonrepeating
。一个nonrepeating timer
触发一次,然后自动失效,从而防止计时器再次触发。相比之下,一个repeating timer
触发,然后在同一个run loop
中重新安排自己。repeating timer
总是根据 计划的触发时间 而不是 实际的触发时间 来调度自己。例如,如果一个timer
计划在某个特定时间触发,并且在该时间之后每隔5秒触发一次,则即使实际触发时间被延迟,计划的触发时间也将始终落在原来的5秒时间间隔上。如果触发时间延迟得太远,以至于超过了计划的触发时间中的一个或多个,则在该时间段内仅触发一次timer
;否则,计时器将被触发一次。 计时器会在触发后重新安排为将来的下一个计划的触发时间。
创建 Timer
有如下三种方式:
方法一
使用此类方法创建 timer
,并在 default Mode
下在当前run loop
上调度 timer
。
1 | class func scheduledTimer(timeInterval ti: TimeInterval, |
1 | weak var timer: Timer? |
1 | if #available(iOS 10.0, *) { |
方法二
使用此类方法创建 timer
,而不会在run loop
上调度它。创建计时器后,必须通过调用相应RunLoop
对象的 add(_:forMode:)
方法将 timer
手动添加到run loop
中。
1 | init(timeInterval ti: TimeInterval, invocation: NSInvocation, repeats yesOrNo: Bool) |
1 | weak var reTimer: Timer? |
1 | if #available(iOS 10.0, *) { |
方法三
使用初始化方法配置 timer
。创建完成后,必须通过调用相应 RunLoop
对象的 add(_:forMode:)
方法将timer
手动添加到 run loop
中。
1 | init(fireAt date: Date, interval ti: TimeInterval, target t: Any, selector s: Selector, userInfo ui: Any?, repeats rep: Bool) |
1 | weak var reTimer: Timer? |
1 | if #available(iOS 10.0, *) { |
注意:
方法一和方法二,默认情况下timer
创建完成后,不会立即触发,而是等待 interval 后再首次触发。当然,你可以通过fire()
或fireDate
设置其首次触发的事件。另外,
timer
会保留target
的强引用,直到它invalidated
。
一旦在run loop
上进行scheduled
(调度),timer
将按指定的时间间隔fires
(触发),直到它invalidated
(无效)为止。nonrepeating timer
在 fires
后立即失效;对于 repeating timer
,必须调用timer
的invalidate()
方法使其无效。
invalidate()
方法会把 timer
从当前 run loop
中删除,因此,你应该始终从安装 timer
的同一线程中调用invalidate()
方法。
使timer
无效会使它立即禁用,以使其不再影响run loop
。然后,run loop
将在invalidate()
方法返回之前或稍后某个时间删除timer
(以及它对timer
的强引用)。timer
一旦invalidated
,timer
对象将无法重用。
repeating timer
触发后,它会在指定的tolerance
(容差)范围内,为最近的 将来日期 安排下一次触发,该日期是上次计划的触发日期之后的时间间隔的整数倍。
如果调出 执行Selector
或invocation
所花费的时间长于指定的间隔,则timer
仅安排下一次触发;也就是说,timer
不会尝试补偿在调用指定的selector
或invocation
时可能发生的任何丢失的触发。
Timer 的内存泄漏
对于 repeating timer
在 iOS10.0 之前类似 target: self, selector:
的API ,由于 repeating timer
被 run loop
强引用,而且 timer
会对 target
强引用。如果开发者未在合适的时机调用 timer
的 invalidate()
方法,则该 target
不会被释放,selector
不会停止执行。
另外,若 target
保留对 timer
的强引用,则会会造成 循环引用。
苹果在 iOS 10.0
之后,给出了使用 闭包 的方式可以避免如上情况的发生,那么在 iOS 10.0
以前,我们可以参考 BlocksKit/BlocksKit 给出的方案:
1 | @implementation NSTimer (BlocksKit) |
对于 swift
的计时器可以学习 SwiftTimer,该库是根据 DispatchSourceTimer
实现的。