讨论weak self不能解决的循环引用
一般情况ViewController的deinit
比如在主ViewController里push进VC2
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func pushBtn(_ sender: Any) {
let vc = VC2()
navigationController?.pushViewController(vc, animated: true)
}
}
VC2里简单看下deninit
class VC2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemCyan
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
}
deinit {
print("VC2 deinit")
}
}
当从VC2返回navigationController时可以看到"VC2 deinit"打印了
加入CADisplayLink
在VC2里加入CADisplayLink,我们知道CADisplayLink跟着屏幕刷新率走,可以搞一些Core Animation事情
class VC2: UIViewController {
var displayLink: CADisplayLink?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemCyan
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
displayLink = CADisplayLink(target: self, selector: #selector(displayLinkSel))
displayLink?.frameInterval = 1
displayLink?.add(to: RunLoop.current, forMode: .default)
}
@objc func displayLinkSel() {
print("displayLinkSel")
}
deinit {
print("VC2 deinit")
}
}
这里运行看到每一frame刷新都打印了"displayLinkSel",但是退出时候"VC2 deinit"没有打印,而且"displayLinkSel"一直继续打印,说明循环引用了 你可能会想用弱引用解决
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
weak var weakSelf = self
displayLink = CADisplayLink(target: weakSelf, selector: #selector(displayLinkSel))
displayLink?.frameInterval = 1
displayLink?.add(to: RunLoop.current, forMode: .default)
}
// 或者让它nil
deinit {
displayLink = nil
print("VC2 deinit")
}
结果还是一样,没有deinit触发,想验证的话可以lldb看下vc2
解决
写一个代理,这里代理直接让他走消息转发机制,类似objc_mesgSend,我们知道iOS里回去class对象和meta-class对象的方法区找,直接转发就是当objc_mesgSend找不到fail掉的最后最后一步,这样提高效率
class MyWeakProxy: NSObject {
weak var target: NSObjectProtocol?
init(target: NSObjectProtocol) {
self.target = target
super.init()
}
override func responds(to aSelector: Selector!) -> Bool {
return (target?.responds(to: aSelector) ?? false) || super.responds(to: aSelector)
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
return target
}
}
然后,我们让target是刚刚写的代理类
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
displayLink = CADisplayLink(target: MyWeakProxy(target: self), selector: #selector(displayLinkSel))
displayLink?.frameInterval = 1
displayLink?.add(to: RunLoop.current, forMode: .default)
}
// 记得让displayLink暂停掉,不然还继续走#selector(displayLinkSel)就会经典的找不到方法runtime错误了。
deinit {
displayLink?.isPaused = true
print("VC2 deinit")
}
最后解释下为什么weak self不起作用,原因不复杂,就是传入时候strong引用了,相当于weakSelf!,这样设计也有道理,因为这玩意和runloop绑定的。
weak var weakSelf = self
displayLink = CADisplayLink(target: weakSelf!, selector: #selector(displayLinkSel))