前言
本文将从如何使用 NSTimer 、 NSTimer 何种情况下会造成循环引用,以及如何避免循环引用几个角度进行介绍。
NSTimer 如何使用
A timer that fires after a certain time interval has elapsed, sending a specified message to a target object.
翻译:一种计时器,在经过一定的时间间隔后触发,向目标对象发送指定的消息。
创建
NSTimer 提供了三种创建方式:
以
scheduledTimerWithTimeInterval
类方法开头的创建实例以
timerWithTimeInterval
开头的创建实例以
init
方法初始化方法创建实例
若采用第一种方式创建,会以 default mode 方式自动加入到当前的 RunLoop 中。
若采用第二、三种方式创建,需手动调用NSRunLoop对象的 addTimer:forMode:
方法。
let timer = Timer.init(timeInterval: 1.0, repeats: true) { timer in
print("This")
}
RunLoop.main.add(timer, forMode: .default)
销毁
invalidate()
该方法是在其加入的 RunLoop 对象中移除timer的唯一方法,同时会 RunLoop 对象会移除其对对象的强引用,若配置了 target 和 user info 对象, timer 也会移除对这些对象的强引用。
为何会造成循环引用
循环引用
考虑一个常见使用 NSTimer 的场景:在 ViewController 中将 timer 作为属性,而 timer 在创建采用了 target-action 的方式,根据 Apple 文档,timer 会对 target 产生强引用,这就产生了有向环,导致循环引用,下图是上例对象引用图:
RunLoop持有timer
还是上例的场景,若在 timer 创建时不采用 target-action ,是不是就可以解决了?
确实可以解决,但还存在一个问题,由于 RunLoop 对象还持有着 timer 对象,这时 ViewController 能被正常释放,但 timer 的引用计数由于不为 1 ,无法被释放,这种情况只是 timer 无法被释放,并不算循环引用范畴,当然若 RunLoop 对象被释放了,则这个 timer 也会被释放掉。
当然此上两种情况都是将 timer 的 repeat
参数设置为 true
时,若为 false
则在定时器第一次触发后,会自动失效,即 RunLoop 会移除对 timer 的强引用, timer 也会移除对 target 和 user info 对象的强引用。
解决方案
合适的时机调用 invalidate()
在上文介绍了invalidate()
, 我们只需要在合适的时机调用 invalidate()
即可。
那么什么是合适的时机呢?
- 若清楚知道什么时候 timer 不再使用,则应立即调用。
- 若不清楚则应破除循环引用,最后在 ViewController 的
deinit
中进行调用。
针对循环引用,需破除有向环
- 采用
weak
关键字修饰 timer ,但需采用scheduledTimerWithTimeInterval
类方法开头的创建实例,因为这个方法会将 timer 加入到 RunLoop 对象中,否则由于weak
修饰, timer 会被自动设为nil
。 - 采用
weak
关键字修饰 target 对象,但需在 block 中进行使用,原理大概是 block 中的 weakSelf 相当于一个临时变量,进而阻止了循环引用。 - 采用中介者模式,让其他对象承担
target
角色,从而阻止循环引用。 - 基于 NSProxy 方式,与 3 类似,通过其他对象来阻止循环引用,需注意的是 NSProxy 无法直接使用。
- 也可直接使用 Apple 提供的新 API:
class func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer
init(timeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void)
convenience init(fire date: Date, interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void)
the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references。
翻译:在执行时,定时器本身作为参数传递给这个块,以帮助避免循环引用。