【问题标题】:Why does [weak self] work but [unowned self] break in a Swift closure?为什么 [weak self] 工作但 [unowned self] 在 Swift 闭包中中断?
【发布时间】:2014-06-25 17:27:46
【问题描述】:

这个 SpriteKit 动作通过使用完成闭包调用自身来重复。它使用闭包,而不是SKAction.repeatActionForever(),因为它需要在每次重复时生成一个随机变量:

class Twinkler: SKSpriteNode {
  init() {
    super.init(texture:nil, color:UIColor.whiteColor(), size:CGSize(width:10.0, height:10.0))
    twinkle()
  }
  func twinkle() {
      let rand0to1 = CGFloat(arc4random()) / CGFloat(UINT32_MAX)
      let action = SKAction.fadeAlphaTo(rand0to1, duration:0.1)
      let closure = {self.twinkle()}
      runAction(action, completion:closure)
  }
}

我想我应该使用[unowned self] 来避免闭包的强引用循环。当我这样做时:

let closure = {[unowned self] in self.twinkle()}

它因错误而崩溃:_swift_abortRetainUnowned。但如果我改用[weak self]

let closure = {[weak self] in self!.twinkle()}

它执行没有错误。为什么[weak self] 工作但[unowned self] 中断?我什至应该在这里使用其中任何一个吗?

Twinkler 对象在程序的其他地方被强烈引用,作为另一个节点的子节点。所以我不明白[unowned self] 参考是如何破坏的。它不应该被释放。

我尝试使用 dispatch_after() 在 SpriteKit 之外复制此问题,但我无法做到。

【问题讨论】:

  • 在 iOS 9.1 上,即使闭包本身没有执行,我也能够重现崩溃。不确定这是否是一个错误,因为它不会发生在 iOS 9.3 stackoverflow.com/a/36274194/3402095

标签: closures swift sprite-kit


【解决方案1】:

如果 self 在闭包中可以为 nil,则使用 [weak self]

如果 self 在闭包中永远不会为零,请使用 [unowned self]

如果在您使用 [unowned self] 时它崩溃了,那么 self 在该闭包中的某个时刻可能为零,因此您需要改用 [weak self]

文档中的示例非常适合阐明在闭包中使用 strongweakunowned

https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

【讨论】:

    【解决方案2】:

    这听起来像是一个错误。 {[unowned self] in self.twinkle()} 应该与 {[weak self] in self!.twinkle()} 工作相同

    【讨论】:

    • 是的。随着更高版本的 Xcode 和 Swift,问题消失了。
    • 而我现在又在 swift 1.2 中体验了它……请注意,崩溃是在声明闭包时发生的,而不是在调用闭包时发生的。
    • 在 Swift 2.2 中遇到了这个问题,使用 weak 而不是 unowned 修复了它。有趣的是,当我检查 self 内部闭包时,它不是 nil
    • @deville:“有趣的是,当我在闭包中检查 self 时,它不是 nil”我认为这可能是因为在纯 Swift 中实现弱引用的方式——弱引用没有设置为nil 直到您尝试访问它们(并且在取消设置所有弱引用之前不会释放未初始化的对象,以允许这样做)。 mikeash.com/pyblog/… 仍然没有解释为什么 weakunowned 之间会有区别。
    【解决方案3】:

    我最近遇到了类似的崩溃。在我的情况下,有时新初始化的对象恰好与释放的对象具有完全相同的内存地址。但是,如果两个对象具有不同的内存地址,则代码将执行得很好。

    所以这是我疯狂的解释。当 swift 对闭包进行强引用并检查其捕获列表时,如果捕获列表中的变量显示“未拥有”,它会检查对象是否已被释放。它不检查对象是否被标记为“弱”。

    由于保证对象在闭包中永远不会为零,因此它实际上永远不会在那里崩溃。

    所以,可能是语言错误。我的看法是使用弱而不是无主。

    【讨论】:

    • 当新的初始化对象与解除分配的对象具有相同的内存地址时,unowned 有完全相同的问题。您是否已经提交了错误报告?
    【解决方案4】:

    为了不出错,应该是:

    let closure = {[weak self] in self?.twinkle()}
    

    不是

    let closure = {[weak self] in self!.twinkle()}
    

    force unwraps 后的感叹号会在 nil 上引发错误。如果 self 为 nil,Unowned 将抛出错误,就像强制展开一样。在执行这两个选项中的任何一个时,您应该使用 and guard 或 if 语句来防止 nil。

    【讨论】:

    • OP 声明第二个示例(强制展开的弱自我)不会引发错误。这个问题不是关于最佳实践,而是为什么在捕获列表中使用 unowned self 会在他的示例中引发错误(以及为什么强制展开弱 self 不会引发错误)。 ;)
    • 我刚刚在操场上尝试过,如果 self 为 nil 则强制解开弱 self 确实会引发错误。因此,如果在运行闭包之前将对象(self)设置为 nil,那么在强制展开它时,像我展示的那样拥有它永远不会抛出错误。 @旋风
    • 我相信你。这就是一切应该如何运作的方式。但是 OP 说它的工作方式不同。为他...另外,如果您有时间,请查看我自己关于类似问题的问题(您可以在原始发帖人问题的评论中找到链接)。
    • 是的,我想我只是在最佳实践上加了两分钱,哈哈@Whirlwind
    【解决方案5】:

    这只是我对文档的阅读,但这是一个理论。

    与弱引用一样,无主引用不会对其引用的实例保持强控制。然而,与弱引用不同的是,无主引用被假定为始终具有值。因此,无主引用总是被定义为 non-optional 类型。 [source]

    您说Twinkler 对象被强烈引用为另一个节点的子节点,但SKNode 的子节点是隐式展开的选项。我敢打赌,问题不在于 self 正在被释放,而是当您尝试创建闭包时,Swift 不愿创建对可选变量的无主引用。因此,[weak self] 是在这里使用的正确闭包捕获列表。

    【讨论】:

    • 保持对 Twinkler 对象的强引用作为场景对象中的非可选常量仍会导致相同的 abortRetainUnowned 运行时崩溃。
    猜你喜欢
    • 1970-01-01
    • 2015-07-02
    • 1970-01-01
    • 2016-05-02
    • 2014-08-02
    • 1970-01-01
    • 2015-09-04
    • 1970-01-01
    • 2019-12-03
    相关资源
    最近更新 更多