为什么 [weak self] 可以工作,但 [unowned self] 会在 Swift 闭包中崩溃?

问题描述 投票:0回答:6

此 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 之外复制此问题,但我无法做到。

closures swift sprite-kit
6个回答
23
投票

如果 self 在闭包中可能为零,请使用 [weak self]

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

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

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

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


9
投票

这听起来像是一个错误。

{[unowned self] in self.twinkle()}
应与
{[weak self] in self!.twinkle()}

作用相同

6
投票

我最近也遇到过类似的事故。就我而言,有时新初始化的对象恰好具有与释放的对象完全相同的内存地址。但是,如果两个对象具有不同的内存地址,则代码将正常执行。

这就是我疯狂的解释。当 swift 将强引用放入闭包并检查其捕获列表时,如果捕获列表中的变量显示“无主”,它会检查该对象是否已被释放。它不会检查对象是否被标记为“弱”。

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

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


3
投票

为了不出现错误,应该是:

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

不是

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

强制展开后的感叹号会引发 nil 错误。如果 self 为零,则 Unowned 会抛出错误,就像强制展开一样。当执行这两个选项中的任何一个时,您应该使用并保护或 if 语句来防止 nil。


1
投票

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

与弱引用一样,无主引用不会对其所引用的实例保持牢固的控制。然而,与弱引用不同的是,无主引用被假定为始终具有值。因此,无主引用始终被定义为“非可选”类型。 [来源]

您说过
Twinkler

对象被强引用为另一个节点的子节点,但

SKNode
的子节点是隐式展开的选项。我敢打赌,问题不在于
self
被释放,而是当你尝试创建闭包时,Swift 不愿创建对可选变量的无主引用。因此,
[weak self]
是此处使用的正确闭包捕获列表。
    


0
投票
https://stackoverflow.com/a/37844485/2269842

import Foundation class WeaklyReferenced { var delayedCallback: DelayedCallback? func runDelayedCallbackRight() { delayedCallback = DelayedCallback() delayedCallback?.delayedCallback { [weak self] in self?.myDuty() } } func runDelayedCallbackWrong() { delayedCallback = DelayedCallback() delayedCallback?.delayedCallback { [weak self] in self!.myDuty() } } func myDuty() { print("This is my duty to print this message") } } class DelayedCallback { func delayedCallback(callback: @escaping () -> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + 2) { callback() } } } class Runner { var weakly: WeaklyReferenced? func runWithoutPrintingRight() { weakly = WeaklyReferenced() weakly?.runDelayedCallbackRight() weakly = nil } func runSuccessfullyRight() { weakly = WeaklyReferenced() weakly?.runDelayedCallbackRight() } func runWrong() { weakly = WeaklyReferenced() weakly?.runDelayedCallbackWrong() weakly = nil } } let runner = Runner() /** The order is relevant: as you can see `runWithoutPrintingRight` will break the reference Running `runWithoutPrintingRight` immediatelly after calling `runSuccessfullyRight` will cause `runSuccessfullyRight` not to print because of the broken reference `runWrong` will cause a crash */ // runner.runWithoutPrintingRight() // This method will break the reference runner.runSuccessfullyRight() // runner.runWrong()

	
© www.soinside.com 2019 - 2024. All rights reserved.