Swift 3 (SpriteKit): Reseting GameScene doesn't deallocate
Asked Answered
M

1

0

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.

Milurd answered 15/1, 2017 at 1:1 Comment(23)
To detect what causing the leak, using Instruments, would be a rather difficult task for you as a beginner. So, what you can do is 1) override deinits of your custom classes, 2) search and find if you are using self inside of closures and see if that is making strong reference cycle somehow (eg. scene retaining the block, and a block retaining a scene because of strong self usage), and 3) remove parts of the code until scene's deinit is called. Also, it is likely that you have multiple leaks... This will not be an easy task, but that is how you would go to solve this.Coccidiosis
Don't get me wrong about "beginner" thing, but I would say you are beginner in SpriteKit due to question you have posted, so I think that methods I proposed would be easier for you in order to solve this.Coccidiosis
Yes you are right; I am a SpriteKit beginner, how should I replace my instances of using self, is there a way to avoid this?Milurd
@Coccidiosis Why is self creating this issue in the first place?Milurd
@Coccidiosis I have actually found a way that makes the scene deallocate now, but now I have a new issue. I have edited my original post with this.Milurd
@Milurd Follow the Whirlwind advices and remove the self.view?.presentScene(nil), you don't need it..Rennes
But that's what is making the GameScene deallocate.Milurd
@AlessandroOrnano I saw that this fixed some other people's problem, but now my screen is blank even after I add the new scene afterwards as in my code.Milurd
@Milurd That's because your probably have removed the scene before to call a new scene, or you have remove the view..Rennes
All what I have is in my post - that's all.Milurd
The deallocation of a scene in SpriteKit is automatic (you shouldn't call DEINIT) when you present a new scene, you don't need to deallocate a SKScene..Rennes
All I do is print "GameScene deinited" with the deinit function to see if the scene does deinit, because before I added self.view?.presentScene(nil), it wasn't called..Milurd
@AlessandroOrnano How would you reset the GameScene?Milurd
Very simple, you reset a GameScene , or generally an SKScene by presenting the same scene.That's because the current scene is deallocated and a new scene (with same SKS or inits methods) is created to your view.Rennes
I think my scene isn't deallocated because I have some retain cycles due to strong references. What's the best way to fix this?Milurd
Whirlwind have just answered to this question. You should search also if you have some protocols/delegate with strong references, if you use third part libraries that create strong references to self for example. I've read a couple of minutes ago that you have just resolved where the scene doesn't deallocate (EDIT WITH NEW ISSUE part)Rennes
Can you see this print about deinit{print("GameScene deinited")} ?Rennes
A bit of confusion, I understand. Well to explain - I began with these retain cycles, added self.view?.presentScene(nil) before it, that deallocated the scene but made the scene blank no matter what, so now I've scrapped that and am trying to remove all strong references. That's why I'm now asking you how to do it.Milurd
I only saw that print when I used self.view?.presentScene(nil) before adding the new scene, but as I said before, it made the scene go blank no matter what ( so it wasn't very useful).Milurd
@Milurd All right, so remove the presentScene(nil) because , as we said, it's useless. Than check all your code to find strong references and if you see that print you should have solved your issue.Rennes
Thank you, I mostly reference to self in closures, so should I just make those closures weak and the variables inside them too?Milurd
You welcome, thank also to @Whirlwind. You could use the code about strong references as explained in this answer because this is considered as a bug.Rennes
@Milurd I completely forgot about Visual Memory Debugger - https://mcmap.net/q/1119617/-knowing-where-retain-cycles-are-and-removing-them. If you are using Xcode 8 and Swift 3, then detection of leaks could be solved in an easier way. Still this doesn't tell you how to fix it, but at least will point you where to look and save your time.Coccidiosis
C
1

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.

Coccidiosis answered 15/1, 2017 at 11:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.