解决Timer(NSTimer in OC)的循环引用
背景
在Apple的对NSTimer的接口
(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
其中 target 有如下说明:
The object to which to send the message specified by aSelector when the timer fires. The target object is retained by the timer and released when the timer is invalidated.
也就是说,除非主动调用timer.invalidated()
(对于repeats = YES的情况; 若repeats = NO, 则在timer执行一次后会自动调用invalidated() ), 否则timer将一直持有这个target(以及其他userInfo等资源). 假设我们在controllerA中定义了一个timer属性,在创建该timer时将target设为controllerA,则构成了循环引用。如果不主动调用invalidated()来打破,即使controllerA被pop出来,其deinit方法也不会被调用,造成内存泄露。
解决方法
import Foundation
// MARK: weak target
private class TargetWrapper{
weak var target: AnyObject? // 因为是weak的,因此可以先被释放
var block: ()->Void
init(_ target: AnyObject?, _ block: @escaping ()->Void) {
self.target = target
self.block = block
}
@objc func execute(_ timer: Timer) {
if target == nil { // 若target对象已被释放,则此时手动invalidate timer,释放timer.
timer.invalidate()
} else {
block()
}
}
deinit {
mp_print("TargetWrapper deinit")
}
}
extension Timer {
static func weakTarget_scheduledTimerWithTimeInterval(_ target: AnyObject, _ ti: TimeInterval, block: @escaping ()->(), repeats: Bool) -> Timer {
let wrapper = TargetWrapper(target, block)
return self.scheduledTimer(timeInterval: ti, target: wrapper, selector: #selector(TargetWrapper.execute(_:)), userInfo: nil, repeats: repeats)
}
}
Timer对该target是弱引用,因此target会被自动释放。此时timer仍然有效,因此TargetWrapper的execute方法仍然会被执行,此时再判断如果target已被释放,手动设置timer.invalidate()以释放timer.
网友评论