I have been trying to reset the GameScene by creating a new duplicate of the GameScene, which works. However, the problem is that the scene doesn't deallocate, which is definitely an issue since I profiled my app with Allocations and saw that the memory was 'clogging' and 'piling' up. This is my code for my GameViewController and my GameScene:
import UIKit
import SpriteKit
import GameplayKit
var screenSize = CGSize()
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
screenSize = scene.size
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
Then my GameScene is basically:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
//Declare and initialise variables and enumerations here
deinit{print("GameScene deinited")}
override func didMove(to view: SKView) {
//Setup scene and nodes
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//Do other things depending on when and where you touch
//When I want to reset the GameScene
let newScene = GameScene(size: self.size)
newScene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
newScene.scaleMode = self.scaleMode
let animation = SKTransition.fade(withDuration: 1.0)
self.view?.presentScene(newScene, transition: animation)
}
"GameScene deinited" is never printed, so the scene never deallocates.
EDIT WITH NEW ISSUE: I have fixed the problem where the scene doesn't deallocate by presenting a nil scene before updating the scene with scene I actually want to show. However, now the screen stays blank even though I present my new scene afterwards. Here's what my code looks like now when I want to reset the GameScene:
self.view?.presentScene(nil)
let newScene = GameScene(size: self.size)
newScene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
newScene.scaleMode = self.scaleMode
let animation = SKTransition.fade(withDuration: 1.0)
self.view?.presentScene(newScene, transition: animation)
Anyone know how to fix this issue?
Thanks for any answers and help.
About presentScene(SKScene?) method
When you call this method, based on what you are passing to it, different things will happen...But first things first... Before scene is presented by an SKView
, you have a newly created scene and an SKView
instance. SKView
instance has scene property, and a scene has a view property. These properties are set accordingly and automatically (scene becomes aware of its view, and view becomes aware of newly created scene) when scene is presented.
Now when you call presentScene(nil)
, the current scene is being removed from a view, so automatically its view property becomes nil (also scene property on an SKView
instance becomes nil).
The Gray Screen
Because the scene has been removed from a view, you see the gray screen now.That is default color of a view. So, no scene currently. And you are going to say, but how? I did:
self.view?.presentScene(newScene, transition: animation)
Well that code didn't crash because of optional chaining. Everything has failed gracefully (line wasn't executed). But now you are going to say:
"Wait, I've seen deinit call!"
That's true. But what you have seen is a deinit
call for the newScene
instance. That is because the line above failed, and the new scene is not presented, and the current method reached the end, and because nothing retaining this new instance, it deallocates. Still, the current scene is not deallocated actually, because if that was the case, you would see two deinit
calls.
Here is how you can check which instance has been deallocated (check memory addresses):
deinit{
print("deinit : \(self) has address: \(Unmanaged.passUnretained(self).toOpaque())")
}
If you print out self.view
after self.presentScene(nil)
line, you will see that self.view
is nil
. And because you are using optional chaining, everything fails gracefully. If you change self.view!.presentScene(newScene)
you will have a crash yelling about finding a nil
while unwrapping the optional.
Calling presentScene(nil) will not resolve leaks
Calling this method, would just set scene property to nil on an SKView
that presents it. It will not deallocate the scene if something retaining it. Say you did some silliness in your GameViewController
, and made a strong reference to the scene that is currently presented (I guess no code needed for this?) so the scene will be retained until GameViewController
deallocates...Means, when you do:
presentScene(nil)
nothing will happen. scene's deinit
will not be called. Remember, this method just set scene property to nil on an SKView
that presents the scene, but if that is not a last strong reference to the currently disposing scene, the scene wont be deallocated (that is how ARC works).
Also, you could have multiple leaks. It is not the scene which leaks. You can have other objects leaking, eg. object A is a property of scene, and object B is a property of a scene. Object A points strongly to Object B, and vice versa. That is strong reference cycle. Now scene will deallocate, but these two objects will stay in memory.
That is why I proposed in comments to override deinit methos on a custom classes.
How to solve a leak?
I would skip going into detail about that because that would be just copy/pasting Apple's documentation and there is a great explanation of how to solve/prevent leaks (Person & Apartment example). But the point is, use weak/unowned references inside of a classes that might retain eachother. Same goes for closures. Define capture lists and use weak/unowned self in the case that scene retains the block, and block retains the scene.
For those interested more into presentScene(SKScene?)
method, there are some quotes from a Technical Note that I've linked below (I wonder why info like this is not a part of docs for presentScene(SKScene?)
method).
There is a mention in Technical Note that says:
SKView maintains a cache of the scenes it presents for performance
reasons
and
Passing a nil argument to SKView presentScene does not cause the scene
to be freed, instead, to release the scene, you must free the SKView
that presented it.