背景:在使用定时器的时候,一不小心就会遇到循环引用问题,导致控制器不会被销毁,定时事件也不会被终止。
错误代码
class ViewController: UIViewController {
var displayLink: CADisplayLink?
// var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
// timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(test), userInfo: nil, repeats: true)
displayLink = CADisplayLink(target: self, selector: #selector(test))
displayLink?.add(to: .current, forMode: .default)
}
@objc func test() {
print("\(#function)")
}
deinit {
print("\(#function)")
displayLink?.invalidate()
// timer?.invalidate();
}
}
分析
如图,控制器Vc强引用displayLink对象,CADisplayLink对象内部的target强引用着Vc,循环引用,谁也别想销毁,导致内存泄漏。
解决办法
target与控制器之间弱引用
因CADisplayLink是系统的类,无法改变target的引用方式,所以可以新建一个中间类,中转target,解决循环引用问题。
控制器将要被释放的时候,发现没有被强引用,控制器被销毁,同时定时器也被销毁,Proxy也被销毁,没有内存泄漏。
Proxy示例代码如下
class Proxy: NSObject {
// 弱指针
weak var target: NSObject?
class func with(target: NSObject) -> Proxy {
let proxy = Proxy()
proxy.target = target
return proxy;
}
/// 重点:消息转发机制
override func forwardingTarget(for aSelector: Selector!) -> Any? {
return target
}
}
Controller变更代码如下
// target的变化
displayLink = CADisplayLink(target: Proxy.with(target: self), selector: #selector(test))
// timer = Timer.scheduledTimer(timeInterval: 1, target: Proxy.with(target: self), selector: #selector(test), userInfo: nil, repeats: true)
总结
一、为什么会产生循环引用?
a. 当使用block
时,没有使用weak self
,block
会对self
强引用。
b. 当使用target
时,NSTimer内部会对self
产生强引用(repeats: YES)
注意:当repeats为NO时,不会产生循环引用
二、如果避免强引用问题?
a. 使用block
+ weak self
的方式,可以避免循环引用
b. 使用target
的时候,使用代理对象,代理对象里面的target
属性对self
弱引用,再利用消息转发机制,将消息转发给self去执行selector方法