问题描述
在这里,我在玩泄漏,所以我特意做了一个强大的参考循环,以查看 Instruments 是否会检测到某些东西,我得到了意想不到的结果.Instruments 中显示的泄漏当然是有道理的,但 随机崩溃 有点神秘(由于我将在后面提到的两个事实).
Here, I was playing with leaks, so I've made a strong reference cycle intentionally to see if the Instruments will detect something, and I got unexpected results. The leak shown in Instruments certainly make sense, but the random crash is a bit mysterious (due to two facts I will mention later).
我这里有一个名为SomeClass
的类:
What I have here is a class called 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
方法实现).
And finally, the MenuScene
which is identical to the GameScene
, just with an empty didMoveToView
method (it has only touchesBegan
method implemented).
重现崩溃
可以通过在场景之间转换几次来重现崩溃.这样做会发生泄漏,因为 someInstance
由 closure
变量保留,而 closure
变量由 someInstance 保留
变量,所以我们有一个循环.但是,这不会导致崩溃(它只会泄漏).当我实际上尝试在闭包中添加 self.method()
时,应用程序崩溃了,我得到了这个:
The crash can be reproduce by transitioning between scenes for a few times. By doing that, the leak will happen because someInstance
is retained by the closure
variable, and the closure
variable is retained by the someInstance
variable, so we have a cycle. But still, this will not produce the crash (it will just leak). When I actually try to add self.method()
inside of a closure, the app crashes and I get this:
还有这个:
如果我尝试访问一个 unowned
引用,而当它引用的对象被释放时,我会产生完全相同的崩溃,例如.当关闭超过捕获的实例时.这是有道理的,但这里不是这样(从不执行闭包).
The exact same crash I can produce if I try to access an unowned
reference when the object it references is deallocated, eg. when closure outlives the captured instance. That make sense, but that is not the case here (closure is never executed).
神秘的部分
神秘之处在于,此崩溃仅在 iOS 9.1 上发生,不在 iOS9.3 上.另一个神秘的事情是,应用程序会随机崩溃,但主要是在前十个转换中.此外,奇怪的是,如果闭包从未执行,或者它捕获的实例未被访问(至少我没有访问),它为什么会崩溃.
The mysterious part is that this crash happens only on iOS 9.1 and not on iOS9.3. And another mysterious thing is, the fact that the app crashes randomly, but mostly within first ten transitions . Also, the weird part is why it crashes if the closure is never executed, or the instance it captures is not accessed (at least not by me).
问题的解决方案,但不是问题的答案
当然,通过打破循环可以通过几种方式解决崩溃,并且我知道只有当我完全确定捕获的实例在初始化后永远不会变为 nil 时,我才应该使用 unowned
.但是,由于我根本没有执行这个关闭,在它超过了场景之后,这个崩溃对我来说非常尴尬.此外,值得一提的是,场景在每次转换后都会被成功定义.
Of course the crash can be solved in a few ways by breaking the cycle, and I am aware that I should use unowned
only when I am completely sure that captured instance will never become nil after initialization. But still, due to fact that I haven't executed this closure at all, after it outlived the scene, this crash is pretty awkward to me. Also, it might be worth of mentioning that scenes are deinited successfully after each transition.
有趣
如果我在捕获列表中使用 weak self
,应用程序不会崩溃(当然泄漏仍然存在).这是有道理的,因为如果场景在块被释放之前变为 nil
,通过可选链接访问场景将防止崩溃.但有趣的是,即使我像这样使用forced unwrapping
,它也不会崩溃:
If I use weak self
inside of a capture list, the app will not crash (the leak still exists of course). Which make sense, because if the scene becomes nil
before block is deallocated, accessing the scene through optional chaining will prevent crash. But the interesting part is that even if I use forced unwrapping
like this, it will not crash:
let closure = {[weak self] in
someInstance.method()
self!.method()
}
这让我想到...感谢有关如何调试此问题的任何提示或有关导致崩溃的原因的解释...
Which makes me think...Appreciate any hints about how to debug this or explanation about what causing the crash ...
这里是 Github repo
Here is the Github repo
推荐答案
发生崩溃是因为 GameScene
对象在动画完成之前已被释放.
the crash is happening because the GameScene
object has been released before the animation finishes.
实现这一点的一种方法是将 SomeClass
对象传回闭包中.
one way to implement this would be to pass the SomeClass
object back in the closure.
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()
}
}
更新
原来这是方便 init?(fileNamed:)
中的细微差别 + [unowned self]
误用导致崩溃的组合.
turns out this is a combination of a nuance in the convenience init?(fileNamed:)
+ misuse of [unowned self]
causing your crash.
虽然官方文档似乎没有说明它,但在此 博文 便利初始化器实际上会重用同一个对象.
although the official documentation doesn't seem to state it, as briefly explained in this blog post the convenience initializer will actually reuse the same object.
文件参考
场景编辑器允许您在不同的 .sks(场景)文件之间引用内容,这意味着您可以将一堆精灵放在一个场景文件中,然后从另一个场景文件中引用该文件.
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) 您可以在多个不同的场景中重复使用相同的精灵集合,这意味着您不必一遍又一遍地重新创建它们.
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) 如果您需要更改所有场景中的引用内容,您所要做的就是编辑原始场景,并且内容会在每个引用它的场景中自动更新.聪明,对吧?
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 会导致一些有趣的输出:
adding logging around the creation, setting of the closure and deinit leads to some interesting output:
GameScene init: 0x00007fe51ed023d0
GameScene setting closure: 0x00007fe51ed023d0
MenuScene deinited
GameScene deinited: 0x00007fe51ed023d0
GameScene init: 0x00007fe51ed023d0
GameScene setting closure: 0x00007fe51ed023d0
注意相同的内存地址是如何被使用两次的.我假设在后台苹果正在做一些有趣的内存管理以进行优化,这可能会导致在 deinit 之后仍然存在陈旧的关闭.
notice how the same memory address is used twice. i'd assume under the hood apple is doing some interesting memory management for optimization which is potentially leading to the stale closure still existing after deinit.
可以对 SpriteKit 的内部进行更深入的挖掘,但现在我只需将 [unowned self]
替换为 [weak self]
.
deeper digging could be done into the internals of SpriteKit, but for now i'd just replace [unowned self]
with [weak self]
.
这篇关于即使块本身未执行,在捕获列表中使用 unowned 也会导致崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!