使用捕获列表中的无主,导致崩溃,甚至块本身不执行

在这里,我在玩漏洞,所以我有意识地做了一个强大的参考周期,看看仪器是否会检测到某些东西,并且我得到了意想不到的结果。 在乐器中显示的泄漏当然是有道理的,但随机的崩溃有点神秘(由于我将在后面提到的两个事实)。

我在这里有一个叫做SomeClass的类:

 class SomeClass{ //As you can guess, I will use this shady property to make a strong cycle :) var closure:(()->())? init(){} func method(){} deinit {print("SomeClass deinited")} } 

另外我有两个场景, GameScene

 class GameScene: SKScene { override func didMoveToView(view: SKView) { backgroundColor = .blackColor() let someInstance = SomeClass() let closure = {[unowned self] in someInstance.method() //This causes the strong reference cycle... self.method() } someInstance.closure = closure } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { if let nextScene = MenuScene(fileNamed: "MenuScene"){ nextScene.scaleMode = .AspectFill let transition = SKTransition.fadeWithDuration(1) view?.presentScene(nextScene, transition: transition) } } deinit {print("GameScene deinited")} func method(){} } 

最后,与GameScene相同的MenuScene ,只有一个空的didMoveToView方法(它只有touchesBegan方法实现)。

重现崩溃

碰撞可以通过在场景之间转换几次来重现。 通过这样做,泄漏将会发生,因为someInstanceclosure closurevariables所保留, closurevariables由someInstancevariables保留,所以我们有一个循环。 但是,这仍然不会产生崩溃(它只会泄漏)。 当我真的尝试添加self.method()self.method() ,应用程序崩溃,我得到这个:

错误信息

和这个:

错误信息

我可以产生完全相同的崩溃,如果我尝试访问一个unowned引用,当它引用的对象被释放,例如。 当closures超过捕获的实例。 这是有道理的,但这不是这种情况(封闭从未执行)。

神秘的一部分

神秘的部分是,这种崩溃发生在iOS 9.1不是iOS9.3上 。 而另一个神秘的事实是,应用程序随机崩溃,但大部分在前十次转换。 此外,怪异的部分是为什么它崩溃,如果封闭永远不会执行,或者它捕获的实例不被访问(至less不是我)。

解决问题而不是解决问题

当然,通过打破循环,可以通过几种方式解决崩溃问题,而且我知道只有当我完全确定捕获的实例在初始化后永远不会变为unowned ,我才应该使用unowned 。 但是,由于事实上我还没有完成这个closures,所以在这个场景失效后,这个崩溃对我来说是非常尴尬的。 此外,值得一提的是,每次过渡后,场景都是成功的。

有趣

如果我在捕获列表中使用weak self ,应用程序不会崩溃(当然,泄漏仍然存在)。 这是有道理的,因为如果在块被释放之前场景变为nil ,通过可选链接访问场景将防止崩溃。 但有趣的是,即使我这样用forced unwrapping ,也不会崩溃:

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

这让我想…赞赏任何有关如何debugging这个或关于什么导致崩溃的解释提示…

编辑:

这是Github 回购

发生崩溃的原因是GameScene对象在animation结束之前已经释放。

实现这一点的一种方法是将SomeClass对象返回到闭包中。

 class SomeClass { var closure: (SomeClass->Void)? } 

这将使用这样的:

 override func didMoveToView(view: SKView) { let someInstance = SomeClass() someInstance.closure = { [unowned self] someClass in someClass.method() // no more retain cycle self.method() } } 

UPDATE

事实certificate,这是一个方便init?(fileNamed:)的细微差别的组合init?(fileNamed:) +滥用[unowned self]导致你的崩溃。

虽然官方文档似乎没有说明,正如在这篇博客文章中简要解释的那样,便捷初始值设定器实际上会重用相同的对象。

文件引用

场景编辑器允许您在不同的.sks(场景)文件之间引用内容,这意味着您可以将一组精灵放在一个场景文件中,然后从另一个场景文件中引用该文件。

你可能会想知道为什么你需要不止一个场景,有几个原因:

1)您可以在多个不同场景中重复使用同一个精灵集合,这意味着您不必一遍又一遍地重新创build它们。

2)如果您需要更改所有场景中的引用内容,您只需编辑原始场景,并在引用该场景的每个场景中自动更新内容。 聪明吧?

在创build时添加日志logging,closures和closures设置导致一些有趣的输出:

 GameScene init: 0x00007fe51ed023d0 GameScene setting closure: 0x00007fe51ed023d0 MenuScene deinited GameScene deinited: 0x00007fe51ed023d0 GameScene init: 0x00007fe51ed023d0 GameScene setting closure: 0x00007fe51ed023d0 

注意如何使用相同的内存地址两次。 我假设内幕苹果正在做一些有趣的内存pipe理的优化,这可能导致陈旧的closures仍然存在后deinit。

可以在SpriteKit的内部进行更深入的挖掘,但是现在我只是用[weak self]代替[unowned self] unowned [weak self]

从我可以看到,如果看起来像保留周期将会造成,因为你是在自己的封闭包括一个对象,并保存到自己。 看下面的工作:

 class GameScene: SKScene { let someInstance = SomeClass() override func didMoveToView(view: SKView) { backgroundColor = .blackColor() let closure = {[weak self, weak someInstance] in someInstance?.method() //This causes the strong reference cycle... self?.method() } someInstance.closure = closure } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { if let nextScene = MenuScene(fileNamed: "MenuScene"){ nextScene.scaleMode = .AspectFill let transition = SKTransition.fadeWithDuration(1) view?.presentScene(nextScene, transition: transition) } } deinit {print("GameScene deinited")} func method(){} } 

我把someInstance移到了这个类的一个属性上,因为我非常肯定在这个块中有一个弱引用,并且没有在函数之外的某个地方传递someInstance,someInstance将在这个函数结束时被取消。 如果这就是你想要的然后保持someInstance里面的function。 如果你愿意的话,你也可以使用无主的,但是正如你所知道的,我想我只是使用弱点的一个更大的爱好者。 让我知道如果修复泄漏和崩溃