Search code examples
iosswiftmemory-leakssprite-kitskscene

Memory leak when presenting SpriteKit scenes


I have an SKScene which is the overworld for my 2D game. It's like a neighborhood that the player can explore. The neighborhood is full of houses. Players can freely enter and exit houses. When the player enters a house, I call skView.presentScene(newHouse) to present the interior of the house as a new scene. When the player exits the house, I call skView.presentScene(overworld) to present the neighborhood again. The player is likely to enter & exit houses many times as they explore the neighborhood. Thus, the neighborhood scene and new instances of the house scene are presented many times.

The problem is that each time I present a house scene, there is a spike in the memory. This is expected since we are loading a new house scene. But when I exit a house scene and return to the neighborhood scene, the amount of memory use does not go down. I've tested this by entering and then exiting houses over and over. The memory use climbs by a consistent amount (~2 MB) every time I enter a house. Eventually, the amount of memory use becomes very large and the game starts dropping frames (but only in the house scenes, not in the neighborhood scene) and eventually becomes unplayable.

Why is this happening?

When working with SpriteKit I thought that best practice is to use presentScene to transition the player to a substantially different "realm" of the game world (e.g. a different level), which is what I believe I am doing here. I also thought that you are not supposed to take any action to "unload" your old scene when you load and then transition to a new scene. Indeed, the SKView documentation does not have any methods for cleaning up unneeded objects from your old scene when you present a new scene. I thought that you are supposed to trust the OS to handle the job of removing the old scene's objects from memory. That is why I simply present new scenes and don't worry about old scenes taking up memory.

I profiled the app in Instruments to check for memory leaks (Instruments is still a bit over my head at this point). I did find some memory leaks that looked very small, but no leaks that seemed to be directly causing the large and consistent memory spikes that are happening each time I present a new house scene.

Am I doing something wrong?

I believe I am taking the correct approach by presenting new scenes and letting the OS handle the job of cleaning up the old scenes. But maybe I am making a mistake in my app design that is causing this memory issue. When I present a new house scene, I pass some information from the neighborhood scene to the new house scene. This information is related to things like the appearance of the house (color, texture, contents, etc.) which is necessary because the houses are procedurally generated: each one is unique. And when I return to the neighborhood scene, I pass some information from the house scene to the neighborhood scene. But maybe the way that I am passing information between scenes is somehow unintentionally causing objects to be retained in memory. If so, what should I do to make sure that this does not happen? Is there some code I should run to clear the unwanted objects from memory when I present a new scene?

I did notice that the SKScene documentation has several methods related to presenting a scene: sceneDidLoad(), willMove(from:), and didMove(to:). Which makes me wonder if I should be using these methods somehow to try and clear unneeded objects from memory when I transition between different scenes.

It may be that my app architecture is simply bad (it's already causing other problems for me). If so, then the solution would be to start by overhauling my app architecture to improve it. So basically, I am trying to determine if my bad app architecture is causing this memory bloat problem, or if the cause is related to SpriteKit and the way that scenes are presented.


Solution

  • First off you are presenting SKScene's correctly and you are correct you should be able to trust that the old scene will get cleaned up. By that I mean there is nothing else you need to do to make it release.

    Now with that being said there are things that you may have done to create a circular reference. Hopefully a couple of these checks will help you track it down.

    The first place I always look is at your player. If your scene has a player property and your player has a scene property this could prevent the scene from deallocating. Player holds on to the scene and scene holds on to player. I doubt this is your case but worth checking.

    Second place to look is there anything else that has a reference or property to that scene? A common issue is when you create your own delegate method. If your player does an action and then calls a method back to that scene. The player should only have weak references to that scene. I have done this myself and seen other do it where they create a custom delegate or protocol and keep a strong reference to it instead of weak.

    Third place to look is anywhere in your code where you call self. This is common in run blocks of SKActions. You may have an action that calls a method on the scene and you may have a property on that scene of that SKAction. The action has reference to the scene due to the run block and the scene has a reference to the action. So look for self and see if that object is a property on that scene.

    Hopefully that helps you track it down. I know it can be frustrating tracking a leak like this down and those are common issues I have seen in the past.