背景:在使用定時(shí)器的時(shí)候,一不小心就會(huì)遇到循環(huán)引用問(wèn)題,導(dǎo)致控制器不會(huì)被銷(xiāo)毀,定時(shí)事件也不會(huì)被終止。
錯(cuò)誤代碼
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強(qiáng)引用displayLink對(duì)象,CADisplayLink對(duì)象內(nèi)部的target強(qiáng)引用著Vc,循環(huán)引用,誰(shuí)也別想銷(xiāo)毀,導(dǎo)致內(nèi)存泄漏。
解決辦法
target與控制器之間弱引用
因CADisplayLink是系統(tǒng)的類(lèi),無(wú)法改變target的引用方式,所以可以新建一個(gè)中間類(lèi),中轉(zhuǎn)target,解決循環(huán)引用問(wèn)題。
控制器將要被釋放的時(shí)候,發(fā)現(xiàn)沒(méi)有被強(qiáng)引用,控制器被銷(xiāo)毀,同時(shí)定時(shí)器也被銷(xiāo)毀,Proxy也被銷(xiāo)毀,沒(méi)有內(nèi)存泄漏。

Proxy示例代碼如下
class Proxy: NSObject {
// 弱指針
weak var target: NSObject?
class func with(target: NSObject) -> Proxy {
let proxy = Proxy()
proxy.target = target
return proxy;
}
/// 重點(diǎn):消息轉(zhuǎn)發(fā)機(jī)制
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)
總結(jié)
一、為什么會(huì)產(chǎn)生循環(huán)引用?
a. 當(dāng)使用block時(shí),沒(méi)有使用weak self,block會(huì)對(duì)self強(qiáng)引用。
b. 當(dāng)使用target時(shí),NSTimer內(nèi)部會(huì)對(duì)self產(chǎn)生強(qiáng)引用(repeats: YES)
注意:當(dāng)repeats為NO時(shí),不會(huì)產(chǎn)生循環(huán)引用
二、如果避免強(qiáng)引用問(wèn)題?
a. 使用block + weak self的方式,可以避免循環(huán)引用
b. 使用target的時(shí)候,使用代理對(duì)象,代理對(duì)象里面的target屬性對(duì)self弱引用,再利用消息轉(zhuǎn)發(fā)機(jī)制,將消息轉(zhuǎn)發(fā)給self去執(zhí)行selector方法