Memory leak when presenting SpriteKit scenes
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.
SpriteKit Memory increase every time new scene presented
I tried checking if things had not been deallocated and this did not solve the problem. I used a developer technical support ticket and an engineer advised me to turn off "GPU Frame Capture" in the scheme for the project.
This 95% solved the problem. Memory usage has reduced to a lot more reasonable amount and the app no longer continues to build up memory usage after I implement reasonable methods to deallocate scenes, nodes etc...
I asked if this solution was only for testing in Xcode and I was told it was not, this is how my app will perform on the App Store:
"GPU Frame Capture is a tool for debugging and is only present when running your app with the Xcode Debugger attached!" - Said the engineer.
SpriteKit game freezing after switching scenes multiple times
By referencing self.destY
in the motionManager.startAccelerometerUpdatesToQueue
closure, you are creating a strong reference cycle. From the docs,
If you assign a closure to a property of a class instance, and the closure captures that instance by referring to the instance or its members, you will create a strong reference cycle between the closure and the instance.
This reference cycle is preventing your scene from being released. And since you do not stop the motion manager, the old managers (plural) are still running when you transition scenes. This is likely causing the current scene to freeze after multiple transitions.
Swift uses capture lists to avoid strong reference cycles, where a capture list has the form
{ [ /* weak or unowned + object, ...*/ ]
/* parameters */ in
}
You can define the capture in a closure as unowned or weak. From the docs,
Define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time.
and
Conversely, define a capture as a weak reference when the captured reference may become nil at some point in the future. Weak references are always of an optional type, and automatically become nil when the instance they reference is deallocated. This enables you to check for their existence within the closure’s body.
Here's an example of how to add a capture list to your accelerometer handler:
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.currentQueue()) {
[unowned self] accelerometerData, error in
var currentY = self.enemySprite.position.y
let acceleration = accelerometerData.acceleration
self.destY = (CGFloat(acceleration.y) * 0.75) + (self.destY * 0.25)
}
Lastly, it's a good idea to stop the accelerometer updates before transitioning scenes
override func willMoveFromView(view: SKView) {
motionManager.stopAccelerometerUpdates()
}
Why does SpriteKit crash when transitioning back and forth between scenes
I found out the mistake I was making.
I was instantiating the nodes outside my scene 2 class, making them global. Therefore when I went from scene 1 -> scene 2 -> scene 1 there was not problem but going then to scene 2 again caused a crash because the nodes were globally created.
Solution:
Moving the following code within the class solved the problem.
var mainLayer = SKSpriteNode()
var labelHolderLayer = SKSpriteNode()
var livesDisplay = SKSpriteNode()
var scoreLabel = SKLabelNode()
SpriteKit not deallocating all used memory
As discussed in the comments, the problem is probably related to a strong reference cycle.
Next steps
- Recreate a simple game where the scene is properly deallocated but some of the nodes are not.
- I'll reload the scene several time. You'll see the scene is properly deallocated but some nodes into the scene are not. This will cause a bigger memory consumption each time we replace the old scene with a new one.
- I'll show you how to find the origin of the problem with Instruments
- And finally I'll show you how to fix the problem.
1. Let's create a game with a memory problem
Let's just create a new game with Xcode based on SpriteKit.
We need to create a new file Enemy.swift
with the following content
import SpriteKit
class Enemy: SKNode {
private let data = Array(0...1_000_000) // just to make the node more memory consuming
var friend: Enemy?
override init() {
super.init()
print("Enemy init")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("Enemy deinit")
}
}
We also need to replace the content of Scene.swift
with the following source code
import SpriteKit
class GameScene: SKScene {
override init(size: CGSize) {
super.init(size: size)
print("Scene init")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
print("Scene init")
}
override func didMove(to view: SKView) {
let enemy0 = Enemy()
let enemy1 = Enemy()
addChild(enemy0)
addChild(enemy1)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let newScene = GameScene(size: self.size)
self.view?.presentScene(newScene)
}
deinit {
print("Scene deinit")
}
}
As you can see the game is designed to replace the current scene with a new one each time the user taps the screen.
Let's start the game and look at the console. Will' see
Scene init
Enemy init
Enemy init
It means we have a total of 3 nodes.
Now let's tap on the screen and let's look again at the console
Scene init
Enemy init
Enemy init
Scene init
Enemy init
Enemy init
Scene deinit
Enemy deinit
Enemy deinit
We can see that a new scene and 2 new enemies have been created (lines 4, 5, 6). Finally the old scene is deallocated (line 7) and the 2 old enemies are deallocated (lines 8 and 9).
So we still have 3 nodes in memory. And this is good, we don't have memory leeks.
If we monitor the memory consumption with Xcode we can verify that there is no increase in the memory requirements each time we restart the scene.
2. Let create a strong reference cycle
We can update the didMove method in Scene.swift like follows
override func didMove(to view: SKView) {
let enemy0 = Enemy()
let enemy1 = Enemy()
// ☠️☠️☠️ this is a scary strong retain cycle ☠️☠️☠️
enemy0.friend = enemy1
enemy1.friend = enemy0
// **************************************************
addChild(enemy0)
addChild(enemy1)
}
As you can see we now have a strong cycle between enemy0 and enemy1.
Let's run the game again.
If now we tap on the screen and the look at the console we'll see
Scene init
Enemy init
Enemy init
Scene init
Enemy init
Enemy init
Scene deinit
As you can see the Scene is deallocated but the Enemy(s) are no longer removed from memory.
Let's look at Xcode Memory Report
Now the memory consumption goes up every time we replace the old scene with a new one.
3. Finding the issue with Instruments
Of course we know exactly where the problem is (we added the strong retain cycles 1 minute ago). But how could we detect a strong retain cycle in a big project?
Let click on the Instrument button in Xcode (while the game is running into the Simulator).
And let's click on Transfer
on the next dialog.
Now we need to select the Leak Checks
Good, at this point as soon as a leak is detected, it will appear in the bottom of Instruments.
4. Let's make the leak happen
Back to the simulator and tap again. The scene will be replaced again.
Go back to Instruments
, wait a few seconds and...
Here it is our leak.
Let's expand it.
Instruments is telling us exactly that 8 objects of type Enemy have been leaked.
We can also select the view Cycles and Root and Instrument will show us this
That's our strong retain cycle!
Specifically Instrument is showing 4 Strong Retain Cycles (with a total of 8 Enemy(s) leaked because I tapped the screen of the simulator 4 times).
5. Fixing the problem
Now that we know the problem is the Enemy class, we can go back to our project and fix the issue.
We can simply make the friend
property weak
.
Let's update the Enemy
class.
class Enemy: SKNode {
private let data = Array(0...1_000_000)
weak var friend: Enemy?
...
We can check again to verify the problem is gone.
Related Topics
How to Duplicate a Sprite in Sprite Kit and Have Them Behave Differently
Swift Progress View with Nstimer
How to Create Objects from Swiftyjson
How to I Turn Off the Ambient Light in Scene Kit (With Swift)
More Concise Way to Nest Enums for Access by Switch Statements in Swift
Uiswipegesturerecognizer Doesn't Recognize Swipe Gesture Initiated Outside the View
What Does This Two Question Mark Mean
Implementing "User Stopped Speaking" Notification for 'Sfspeechrecognizer'
Swiftui Inputaccesoryview Implementation
Nstableview Inside Nspopover Looks Different as Standalone
Swiftui Sheet Not Updating Variable
From the Swift Repl, How to Get a List of Available Modules
Swift Combine Publishers VS Completion Handler and When to Cancel
How to Make Embedded View Controller Part of the Responder Chain
From Any Utf-16 Offset, Find the Corresponding String.Index That Lies on a Character Boundary