使用捕获列表中的无主,导致崩溃,甚至块本身不执行
在这里,我在玩漏洞,所以我有意识地做了一个强大的参考周期,看看仪器是否会检测到某些东西,并且我得到了意想不到的结果。 在乐器中显示的泄漏当然是有道理的,但随机的崩溃有点神秘(由于我将在后面提到的两个事实)。
我在这里有一个叫做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
方法实现)。
重现崩溃
碰撞可以通过在场景之间转换几次来重现。 通过这样做,泄漏将会发生,因为someInstance
被closure
closure
variables所保留, closure
variables由someInstance
variables保留,所以我们有一个循环。 但是,这仍然不会产生崩溃(它只会泄漏)。 当我真的尝试添加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。 如果你愿意的话,你也可以使用无主的,但是正如你所知道的,我想我只是使用弱点的一个更大的爱好者。 让我知道如果修复泄漏和崩溃