此 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 之外复制此问题,但我无法做到。
如果 self 在闭包中可能为零,请使用 [weak self]。
如果 self 在闭包中永远不会为零,请使用 [unowned self]。
如果当您使用 [unowned self] 时崩溃,那么 self 在该闭包中的某个时刻可能为零,因此您需要使用 [weak self] 代替。
文档中的示例非常适合阐明在闭包中使用 strong、weak 和 unowned:
https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
这听起来像是一个错误。
{[unowned self] in self.twinkle()}
应与 {[weak self] in self!.twinkle()}
作用相同
我最近也遇到过类似的事故。就我而言,有时新初始化的对象恰好具有与释放的对象完全相同的内存地址。但是,如果两个对象具有不同的内存地址,则代码将正常执行。
这就是我疯狂的解释。当 swift 将强引用放入闭包并检查其捕获列表时,如果捕获列表中的变量显示“无主”,它会检查该对象是否已被释放。它不会检查对象是否被标记为“弱”。
由于保证对象在闭包中永远不会为零,因此它永远不会真正在那里崩溃。
所以,可能是一个语言错误。我的看法是使用弱而不是无主。
为了不出现错误,应该是:
let closure = {[weak self] in self?.twinkle()}
不是
let closure = {[weak self] in self!.twinkle()}
强制展开后的感叹号会引发 nil 错误。如果 self 为零,则 Unowned 会抛出错误,就像强制展开一样。当执行这两个选项中的任何一个时,您应该使用并保护或 if 语句来防止 nil。
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()