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).
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")}
}
Also I have two scenes, the 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(){}
}
And finally, the MenuScene
which is identical to the GameScene
, just with an empty didMoveToView
method (it has only touchesBegan
method implemented).
Reproducing the Crash
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:
and this:
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).
The Mysterious Part
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).
Solution to the Problem but not the Answer to the Question
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.
Interesting
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 ...
EDIT:
Here is the Github repo