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 实现的。