【问题标题】:Why can i use self when I initialize property with a closure?当我用闭包初始化属性时,为什么我可以使用 self ?
【发布时间】:2018-05-18 00:48:39
【问题描述】:

官方文档(Swift 4.1)说:

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

所以我想我需要使用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 呢?是否有一些变化,或者我错过了什么?

【问题讨论】:

  • 你代码中的目标可能是nil,这可能起作用的原因是:"...如果你指定nil,UIKit会在响应者链中搜索响应的对象到指定的操作消息并将消息传递给该对象”(来自方法的文档)
  • 我很困惑,您的代码使用的是let,但在您自己的问题中,您也在谈论lazy var。那么它是哪一个?
  • @Honey OP 了解lazy var 是解决问题的正确方法。问题是为什么提供的 sn-p 工作,即使他不使用 lazy var
  • @Honey 我只是没有使用lazy var,而是使用了let
  • @N.Khasanov 您可以通过在控制器初始化期间或之后设置断点并检查 _targetActions 数组(在调试器的变量视图中)来验证目标实际上是 nil按钮。在此示例中,loginRegisterButton 在控制器初始化期间设置,这意味着还没有self(换句话说,您不能保证此时所有内容都已初始化)。 lazy 解决这个问题的方式是实际分配在第一次访问时被延迟。

标签: ios swift


【解决方案1】:

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

...如果指定 nil,UIKit 会在响应者链中搜索 响应指定操作消息并传递 向该对象发送消息

(来自documentation of the method

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

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

【讨论】:

    【解决方案2】:

    奇怪——也许是错误的——闭包中的 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-creating 闭包持有对新分配函数的强引用。

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

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

    当我们向按钮询问allTargets 时,它会构造一个NSSet。由于NSSet 不能直接保持nil,所以按钮将+[NSNull null] 放在集合中。

    【讨论】:

    • selfNSObjectProtocolself() 实例方法,柯里化为(Self) -&gt; () -&gt; Self,因为Swift 将实例属性初始化器视为在类型的静态范围内(即您可以直接引用静态成员,and get curried instance methods)。如果你在一个没有继承自 NSObject 的类中执行此操作,则会收到关于使用 self 的错误。
    • 有趣的观察!与此相关的另一件事是,如果您执行let s = self 之类的操作,类型推断系统会将类型报告为(MyViewController) -&gt; () -&gt; MyViewController
    • aaand @Hamish 在我发表评论时已经澄清了这一点......
    • 我记得这在以前的 Swift 版本中给出了一个错误。那么现在发生了什么变化?
    • @robmayoff 现在我明白了你的意思并理解了你的回答,谢谢!
    猜你喜欢
    • 2016-12-18
    • 1970-01-01
    • 2020-04-18
    • 2020-07-13
    • 2023-03-26
    • 2020-08-15
    • 2017-12-02
    • 2017-01-26
    • 1970-01-01
    相关资源
    最近更新 更多