闭包实现属性初始化:ARC强引用循环与self未解析问题咨询
一、为什么闭包内addTarget(self)没有产生强引用循环?
这个问题的核心在于UIKit的Target-Action机制对target的引用特性,以及代码中的引用链关系:
Target-Action的引用本质:
UIKit中的控件(比如UIButton)在调用addTarget(_:action:for:)时,内部对传入的target持有的是弱引用(weak),而非强引用。也就是说,按钮并不会主动维持self的存活,当self被释放时,按钮对self的引用会自动失效。引用链的闭环检查:
假设你的代码是常见的场景:class CustomView: UIView { // self 强引用 button private let button = UIButton(type: .system) override init(frame: CGRect) { super.init(frame: frame) let setupClosure = { self.button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) self.addSubview(self.button) } setupClosure() } @objc private func buttonTapped() { /* ... */ } }这里的引用链是:
self→button(强引用),而button→self(弱引用)。强引用循环的必要条件是双向强持有,这里显然没有形成闭环,所以ARC可以正常回收这两个对象。哪怕闭包是逃逸闭包,只要
button对self是弱引用,整个链条就不会触发循环引用问题。
二、为什么移除UIView继承后,编译器报错“Use of unresolved identifier 'self'”?
这个问题和Swift类的初始化安全规则直接相关,UIView子类之所以能正常使用self,是因为它的初始化流程满足了Swift对self访问的严格要求:
Swift初始化的两阶段规则:
Swift对类的初始化有严格的安全检查,分为两个阶段:- 阶段1:必须先初始化所有存储属性,调用父类的初始化器(如果是子类),确保对象的内存完全分配并初始化。
- 阶段2:只有完成阶段1后,才能访问
self、调用实例方法或修改属性。
UIView子类的合法访问原因:
当你继承UIView时,通常会在init(frame:)或init?(coder:)中先调用super.init(...)——这一步直接完成了阶段1的要求,此时self已经是完全初始化的状态,所以你在后续的闭包(哪怕是初始化器内的闭包)中访问self是合法的。普通类的报错原因:
如果你移除了UIView继承,变成一个普通Swift类,假设代码如下:class CustomClass { private let button = UIButton(type: .system) init() { let setupClosure = { // 报错:Use of unresolved identifier 'self' self.button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) } setupClosure() } @objc private func buttonTapped() { /* ... */ } }问题出在:普通类的
init()中,你还没有完成所有初始化流程的安全校验,Swift认为self仍处于“未完全就绪”的状态,因此不允许你在闭包中引用它。解决这个问题的方法也很简单:要么把闭包标记为
@escaping,要么使用lazy属性(lazy属性的闭包会在第一次访问时执行,此时self已经完全初始化),就能正常访问self了。
内容的提问来源于stack exchange,提问作者Sasha Kolsky

