我这里有一个名为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(){} }
最后,MenuScene与GameScene完全相同,只是使用了一个空的didMoveToView方法(它只实现了touchesBegan方法).
再现崩溃
可以通过在场景之间转换几次来重现崩溃.通过这样做,泄漏将发生,因为someInstance由闭包变量保留,并且闭包变量由someInstance变量保留,因此我们有一个循环.但是,这仍然不会产生崩溃(它只会泄漏).当我真的尝试在闭包内添加self.method()时,应用程序崩溃了,我得到了这个:
还有这个:
如果我试图在它引用的对象被释放时尝试访问无主参考,我可以产生完全相同的崩溃,例如.当闭包超过捕获的实例时.这是有道理的,但这不是这里的情况(封闭永远不会执行).
神秘的部分
神秘的部分是这次崩溃只发生在iOS 9.1而不是iOS9.3上.另一个神秘的事情是,应用程序随机崩溃,但大多数在前十个转换中.此外,奇怪的部分是如果从不执行闭包,或者没有访问它捕获的实例(至少不是我),它崩溃的原因.
问题的解决方案而不是问题的答案
当然崩溃可以通过几种方式解决周期,我知道只有当我完全确定捕获的实例在初始化后永远不会变为零时我才应该使用unowned.但是,由于事实上我没有完成这个关闭,在它失去现场之后,这次崩溃对我来说非常尴尬.此外,值得一提的是,每次过渡后场景都会成功消失.
有趣
如果我在捕获列表中使用弱自我,应用程序将不会崩溃(当然泄漏仍然存在).这是有道理的,因为如果在取消分配块之前场景变为零,则通过可选链接访问场景将防止崩溃.但有趣的是,即使我使用这样的强制解包,它也不会崩溃:
let closure = {[weak self] in someInstance.method() self!.method() }
这让我觉得……欣赏有关如何调试此内容的任何提示或有关导致崩溃的原因的解释……
编辑:
这是Github repo
解决方法
实现这一点的一种方法是将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
事实证明,这是一个细微差别的组合在一起?(fileNamed :)滥用[无主自我]造成你的崩溃.
虽然官方文档似乎没有说明,但正如在blog post中简要解释的那样,便利初始化器实际上将重用相同的对象.
File References
The scene editor allows you to reference content between different .sks (scene) files,meaning you can put together a bunch of sprites in a single scene file and then reference the file from another scene file.
You might wonder why you would need more than one scene,and there a couple of reasons:
1) You can reuse the same collection of sprites in multiple different scenes,meaning you don’t have to recreate them over and over again.
2) If you need to change the referenced content in all of your scenes,all you have to do is edit the original scene and the content automatically updates in every scene that references it. Smart,right?
在创建周围添加日志记录,设置闭包和deinit会产生一些有趣的输出:
GameScene init: 0x00007fe51ed023d0 GameScene setting closure: 0x00007fe51ed023d0 MenuScene deinited GameScene deinited: 0x00007fe51ed023d0 GameScene init: 0x00007fe51ed023d0 GameScene setting closure: 0x00007fe51ed023d0
注意两次使用相同的内存地址.我假设在引擎盖下苹果正在做一些有趣的内存管理以进行优化,这可能导致在deinit之后仍然存在过时的关闭.
可以对SpriteKit的内部进行更深入的挖掘,但是现在我只用[弱自我]替换[无主自我].