You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

闭包实现属性初始化:ARC强引用循环与self未解析问题咨询

解答你的ARC与Swift类初始化问题

一、为什么闭包内addTarget(self)没有产生强引用循环?

这个问题的核心在于UIKit的Target-Action机制对target的引用特性,以及代码中的引用链关系:

  1. Target-Action的引用本质:
    UIKit中的控件(比如UIButton)在调用addTarget(_:action:for:)时,内部对传入的target持有的是弱引用(weak),而非强引用。也就是说,按钮并不会主动维持self的存活,当self被释放时,按钮对self的引用会自动失效。

  2. 引用链的闭环检查:
    假设你的代码是常见的场景:

    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访问的严格要求:

  1. Swift初始化的两阶段规则:
    Swift对类的初始化有严格的安全检查,分为两个阶段:

    • 阶段1:必须先初始化所有存储属性,调用父类的初始化器(如果是子类),确保对象的内存完全分配并初始化。
    • 阶段2:只有完成阶段1后,才能访问self、调用实例方法或修改属性。
  2. UIView子类的合法访问原因:
    当你继承UIView时,通常会在init(frame:)或init?(coder:)中先调用super.init(...)——这一步直接完成了阶段1的要求,此时self已经是完全初始化的状态,所以你在后续的闭包(哪怕是初始化器内的闭包)中访问self是合法的。

  3. 普通类的报错原因:
    如果你移除了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:14:08