Why doesn't sknode.scene induce a retain cycle?

Why doesn't sknode.scene induce a reference cycle with skscene.children? That is, skscene refers to a particular sknode through the node tree and the sknode refers back to the root skscene.


Yet, when another scene is presented the current scene deinit method is called. In the deinit method, I can print out the sknode.scene and see that it still points to the scene that is being deinit.


How can this be?

Accepted Reply

I think it's incorrect to try to reason about the scene's handling of running actions. You have to assume that Sprite Kit doesn't introduce any retain cycles of its own, that it can't break at a suitable time.


Your task is to reason about retain cycles you might introduce. Your theory is that (without the weak childNode) there is a retain cycle from childNode to moveToAction to the closure and back to childNode. You can't be certain, but it does seem likely, and so a weak capture seems like a good idea.


However, the action has a duration that ends, and the game scene may automatically cancel actions when it knows it can't continue to run them, so it's possible that the cycle is manually broken for you, at one point or another. The fact that this happens in a particular case might save you from a memory leak (or worse), but I would neither rely on being saved, nor try to reason about when you might be saved.


Therefore, a weak capture seems like the right thing to do here.

Replies

This would only be inexplicable if you knew that "scene" (or "parent", for that matter) was a stored property of SKNode. If it's a computed property, then there's nothing preventing it from being backed by a private stored property (or private instance variable, since it's an Obj-C class) that is weak rather than strong.


This conclusion is slightly supported by the fact there is no setter for "scene" (or "parent").


There's even a reason why this slightly odd arrangement should exist. Removing a node from the scene from a scene (presumably) means removing it from its parent, and vice versa. In addition, a node that's in use by a SKAction probably should stay alive until the action no longer needs it. So, there are plenty of reasons why the lifetime of the node cannot be controlled by "scene", "parent" or even "children" without some supervisory logic. These are all implementation details, so it makes sense that they should be hidden away to some degree.

Thanks for your reply.


It seems that the weakness of variables should be documented since it seems to be needed for setting up closures.


The following is the problem that I am having:


class GameScene:SKScene {
    override func willMove(from view: SKView) {
        ...
        do { // note this; is this overkill ???
            func removeAllActionsForNode(_ node:SKNode) {
                node.removeAllActions()
                for childNode in node.children {
                    removeAllActionsForNode(childNode)
                }
            }
            removeAllActionsForNode(self)
        }
    }
    func doSomeAction() {
        ...
        let moveToAction = SKAction.move(to: ..., duration: ...)
        childNode.run(moveToAction) { [weak self, weak childNode] in
            guard let childNode = childNode else { return }
            self?.doSomeCompletion(childNode: childNode)
        }
    }
}


I want to insure that the deinit method of GameScene is called when I present a new scene even if actions are running. The code above seems to solve this problem, but I don't see why it really works. In other words, it seems like it is overkill.


First, consider the "willMove" segment of code. Do the actions for childNode get remove before completed? If not, then the current gameScene will be removed from the view and its update method will no longer be called and the actions with durations will not complete and it would then seem that the current gameScene will have retain cycles and won't be deleted. However, in my testing, it seems that the "willMove" segment of code will get rid of all the uncompleted actions and the current gameScene will deinit.


Second, consider the "doSomeAction" segment of code. It seems that "weak childNode" and the "guard" are not needed. This is why I asked my original question. It doesn't hurt to have "weak childNode", but I didn't understand why deinit would be called for the current gameScene if I left it out.

>should be documented


Feel free to file bugs against the docs via the report bugs link, below right, adding your report # to your thread for reference, thanks and good luck.

I think it's incorrect to try to reason about the scene's handling of running actions. You have to assume that Sprite Kit doesn't introduce any retain cycles of its own, that it can't break at a suitable time.


Your task is to reason about retain cycles you might introduce. Your theory is that (without the weak childNode) there is a retain cycle from childNode to moveToAction to the closure and back to childNode. You can't be certain, but it does seem likely, and so a weak capture seems like a good idea.


However, the action has a duration that ends, and the game scene may automatically cancel actions when it knows it can't continue to run them, so it's possible that the cycle is manually broken for you, at one point or another. The fact that this happens in a particular case might save you from a memory leak (or worse), but I would neither rely on being saved, nor try to reason about when you might be saved.


Therefore, a weak capture seems like the right thing to do here.