平时使用Timer/NSTimer时候, 由于Timer会持有self, 所以释放一直是一个麻烦.
本文不再对于Timer初始化, 需要加入Runloop等事项进行讨论了, 使用scheduledTimer默认加入Runloop.
先来段看起来正确, 却无法释放的错误写法
class TimerViewController: UIViewController {
lazy var timer: Timer = {
return Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(timeRun),
userInfo: nil,
repeats: true)
}()
override func viewDidLoad() {
super.viewDidLoad()
timer.fire()
}
@objc func timeRun() {
print(#function)
}
deinit() {
/// WARNING: 此处不可能执行了, 因为timer持有了self
/// 手动去释放 timer.invalidate()的话, 比如remove或者点击按钮等, 也不是很好
timer.invalidate()
}
}
解决思路就是打破这个引用, 也就是Timer不再持有self.
让Timer随着self生命周期一起走, 尽量保证原始API
先来一个效果代码, 注意观察Timer的初始化.safe_scheduled
class TimerViewController: UIViewController {
lazy var timer: Timer = {
return Timer.safe_scheduled(interval: 1,
target: self,
selector: #selector(timeGo),
repeats: true,
userInfo: nil)
}()
override func viewDidLoad() {
super.viewDidLoad()
timer.fire()
}
@objc func timeRun() {
print( #function)
}
deinit() {
print( #function)
}
}
具体实现细节
extension Timer {
/// 此Timer不可在deinit/dealloc中执行invalidate等任何操作, 已经随着target生命周期释放了
static func safe_scheduled(interval: TimeInterval,
target: Any,
selector: Selector,
repeats: Bool,
userInfo: Any? = nil) -> Timer {
return WolfTimer(scheuled: interval,
target: target,
selector: selector,
userInfo: userInfo,
repeats: repeats).k_timer ??
Timer.scheduledTimer(timeInterval: interval,
target: target,
selector: selector,
userInfo: userInfo,
repeats: repeats)
}
}
class WolfTimer {
convenience init(scheuled interval: TimeInterval,
target: Any,
selector: Selector,
userInfo: Any?, repeats: Bool) {
self.init()
k_target = target as? NSObjectProtocol
k_selector = selector
k_timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(timer_run), userInfo: userInfo, repeats: repeats)
}
weak var k_timer: Timer?
weak var k_target: NSObjectProtocol?
var k_selector: Selector?
@objc func timer_run() {
guard let target = k_target,
let selector = k_selector,
target.responds(to: selector) else {
k_timer?.invalidate()
return
}
target.perform(selector, with: k_timer)
}
deinit {
print(#function + "WolfTimer")
}
}
也就是说, ViewController中的Timer被vc所retain持有,
Timer持有WolfTimer的对象.
Timer执行计时器, 则由WolfTimer根据target和selector去perform执行.
如果ViewController被释放, 则由于
weak var k_target: NSObjectProtocol?
weak var k_timer: Timer?
是weak的弱引用, 而也会变为nil, 也就释放了.
mark注意点: 原本觉得k_timer可以不为weak, 但是考虑到timer可能并没有执行, 那么WolfTimer也就无法释放了, 我就试了试, 结果发现另外一个坑, 如果k_timer不使用weak, 那么如果ViewController的deinit中如果执行timer.invalidate()或者其他方法, 会触发VC过度释放.
从大神处学到了一种新的做法.
感谢 LEE大神
Timer初始化时候, 使用WeakObjc(self)
class TimerViewController: UIViewController {
lazy var timer: Timer = {
return Timer.scheduledTimer(timeInterval: 1,
target: WeakObjc(self),
selector: #selector(timeRun),
userInfo: nil,
repeats: true)
}()
override func viewDidLoad() {
super.viewDidLoad()
timer.fire()
}
@objc func timeRun() {
print(#function)
}
}
class WeakObjc: NSObject {
private weak var target: AnyObject?
init(_ target: AnyObject) {
self.target = target
super.init()
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
return target
}
override func responds(to aSelector: Selector!) -> Bool {
return target?.responds(to: aSelector) ?? super.responds(to: aSelector)
}
override func method(for aSelector: Selector!) -> IMP! {
return target?.method(for: aSelector) ?? super.method(for: aSelector)
}
override func isEqual(_ object: Any?) -> Bool {
return target?.isEqual(object) ?? super.isEqual(object)
}
override func isKind(of aClass: AnyClass) -> Bool {
return target?.isKind(of: aClass) ?? super.isKind(of: aClass)
}
override var superclass: AnyClass? {
return target?.superclass
}
override func isProxy() -> Bool {
return target?.isProxy() ?? super.isProxy()
}
override var hash: Int {
return target?.hash ?? super.hash
}
override var description: String {
return target?.description ?? super.description
}
override var debugDescription: String {
return target?.debugDescription ?? super.debugDescription
}
deinit { print("deinit:\t\(classForCoder)") }
}
感谢 LEE大神