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

未使用lazy关键字时,button.addTarget(self, action: #selector(taptap))为何可运行?

为什么未添加lazy关键字时,闭包初始化的UIButton调用addTarget(self, ...)仍能正常工作?

先来看你提供的代码:

class MyView: UIView {
    let button: UIButton = {
        let button = UIButton()
        button.addTarget(self, action: #selector(taptap), for: .touchUpInside)
        print(self) // (Function)
        // button.frame = bounds <- Cannot assign here
        return button
    }()
    lazy var button2: UIButton = {
        let button = UIButton()
        print(self) // <MyView: 0x...>
        return button
    }()
    
    @objc func taptap() {
        // 点击事件处理
    }
}

这个问题的核心在于非lazy存储属性闭包的执行时机,以及UIKit target-action机制的工作方式,我来一步步拆解:

1. 非lazy属性闭包的执行时机

非lazy的存储属性闭包是在实例初始化的早期阶段执行的——此时MyView的实例还没有完成所有属性的初始化,所以你无法访问self.bounds这类依赖实例完全初始化的属性(这就是button.frame = bounds报错的原因)。但注意:此时self已经是一个有效的内存地址,它指向正在被构建的实例本身,只是这个实例还没完全“组装好”。

2. 为什么addTarget(self, ...)能正常工作?

UIKit的addTarget(_:action:for:)方法只是把你传入的self(正在初始化的实例引用)保存了下来。虽然闭包执行时实例还没完全初始化,但这个引用的内存地址是有效的。当整个MyView实例初始化完成后,这个保存的引用就指向了一个完整、可用的实例。等到用户点击按钮时,实例早已就绪,所以#selector(taptap)对应的方法能被正常调用。

至于你看到print(self)输出(Function),这是Swift在这个特殊阶段的小细节:在非lazy属性的默认值闭包中,self的上下文比较特殊,此时它会被解析为类型的构造器函数,而非实例本身——但这并不影响addTarget的工作,因为方法最终只需要实例的内存地址,而这个地址已经被正确传递了。

3. 对比lazy属性的差异

lazy属性的闭包是在第一次访问该属性时才执行的,此时MyView的实例已经完全初始化完成。所以self就是完整的实例,你可以访问bounds这类属性,print(self)也会输出正常的实例描述(比如<MyView: 0x123456>)。

内容的提问来源于stack exchange,提问作者ccoroom

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:46:35