当我用闭包初始化属性时,为什么我可以使用self?

时间:2018-05-17 13:56:15

标签: ios swift

官方文档(Swift 4.1)说:

  

如果使用闭包初始化属性,请记住其余部分   该实例尚未初始化   关闭执行。这意味着您无法访问任何其他   封闭内的属性值,即使这些属性也是如此   有默认值。你也不能使用隐式自我属性,   或者调用任何实例的方法。

所以我想我需要使用lazy var,今天我创建了一个按钮

let loginRegisterButton: UIButton = {
    let button = UIButton(type: .system)
    button.backgroundColor = UIColor.rgb(red: 80, green: 101, blue: 161)
    button.setTitle("Register", for: .normal)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setTitleColor(UIColor.white, for: .normal)
    button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
    button.addTarget(self , action: #selector(handleRegister), for: .touchUpInside)
    return button
}()

所以我只是在addTarget方法中使用self而XCode似乎并没有打扰更多的程序没有任何错误。一切都很好。所以当我用闭包初始化属性时,为什么我可以使用self?是否有一些变化或者我错过了什么?

2 个答案:

答案 0 :(得分:4)

很奇怪 - 也许是buggily-闭包中的self计算为一个函数对象。 (Hamish在评论中解释说,该函数是NSObjectProtocol.self的一种咖喱形式。)但是,当您稍后询问按钮的目标时,它会报告+[NSNull null]

let loginRegisterButton: UIButton = {
    let button = UIButton(type: .system)
    button.backgroundColor = .red
    button.setTitle("Register", for: .normal)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setTitleColor(UIColor.white, for: .normal)
    button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)

    print(self)
    // Output: (Function)

    button.addTarget(self, action: #selector(handleRegister), for: .touchUpInside)

    let target = button.allTargets.first!.base
    print(target, type(of: target))
    // Output: <null> NSNull

    return button
}()

(使用Xcode 9.3.1测试。)

该函数变为NSNull,如下所示:函数对象在调用addTarget之前分配,作为target参数传递。 loginRegisterButton - 创建闭包具有对新分配函数的强引用。

该按钮将目标存储为归零弱引用。

addTarget返回后,闭包释放其对该函数的强引用。这是该函数的唯一强引用,因此Swift释放该函数。这会将按钮对函数的弱引用设置为nil。

当我们询问allTargets按钮时,它会构建NSSet。由于NSSet无法直接保留nil,因此按钮会将+[NSNull null]放入集合中。

答案 1 :(得分:3)

您的代码中的目标是可能 nil,这可能起作用的原因是:

  

...如果你指定nil,UIKit会在响应者链中搜索一个   响应指定操作消息并传递的对象   消息到该对象

(来自documentation of the method

您可以通过在控制器初始化期间或之后设置断点并检查按钮的nil数组(在调试器的变量视图中)来验证目标是否为_targetActions(您可以看到) target的地址为0x0)。

在您的示例中,loginRegisterButton在控制器初始化期间设置,这意味着还没有self(换句话说,您无法保证此时所有实例变量都已初始化)。 lazy解决这个问题的方法是在第一次访问时推迟实际的分配。