iOS 7 Sprite Kit freeing up memory
Asked Answered
L

7

21

I am building an iOS game aimed for the new iOS 7 and Sprite Kit, using emitter nodes and physics to enhance gameplay. While developing the app, I ran into a serious problem: you create your scenes, nodes, effects, but when you are done and need to return to the main screen, how do you free up all the memory allocated by these resources?

Ideally ARC should free up everything and the application should get back to the memory consumption level it had before creating the scene, but this is not what happens.

I've added the following code, as the dealloc method of the view, which draws the scene and is responsible for removing everything upon getting closed (removed):

- (void) dealloc
{
    if (scene != nil)
    {
        [scene setPaused:YES];

        [scene removeAllActions];
        [scene removeAllChildren];

        scene = nil;

        [((SKView *)sceneView) presentScene:nil];

        sceneView = nil;
    }
}
  • sceneView is a UIView, which is the container of the scene
  • scene is an extension of the SKScene class, creating all the SKSpriteNode objects

I would very much appreciate any help on this matter.

Lillis answered 8/10, 2013 at 15:1 Comment(2)
Same issue here, my scene keeps running even when I call dismissViewControllerAnimated, did you manage to fix it?Squire
Yes, I did, there wasn't anything I could do about it from the scene or Sprite Kit for that matter, I simply needed to remove the scene and the view containing it completely from the parent view, cut all its bonds to the other parts of the system, in order for the memory to be deallocated as well.Lillis
L
20

I had a lot of memory problems with Sprite Kit, and I used a tech support ticket to get info, and it may relate here. I was asking whether starting a new SKScene would totally release all the memory the previous one used. I found out this:

The underlying memory allocated by +textureWithImageNamed: may or may not (typically not) be released when switching to new SKScene. You cannot rely on that. iOS releases the memory cached by +textureWithImageNamed: or +imageNamed: when it sees fit, for instance when it detects a low-memory condition.

If you want the memory released as soon as you’re done with the textures, you must avoid using +textureWithImageNamed:/+imageNamed:. An alternative to create SKTextures is to: first create UIImages with +imageWithContentsOfFile:, and then create SKTextures from the resulting UIImage objects by calling SKTexture/+textureWithImage:(UIImage*).

I don't know if this helps here.

Lindsy answered 4/2, 2014 at 2:19 Comment(0)
W
9

All of that code is superfluous. Provided that you have no memory leaks or retain cycles in your code, once you release the Sprite Kit view everything will be cleared from memory.

Under the hood Sprite Kit employs a caching mechanism but we have no way of controlling it, nor should we need to if it is properly implemented (which is safe to assume).

If this is not what you're seeing in Instruments, check for retain cycles, leaks. Verify that dealloc of the scene and view does get called. Make sure no strong references to view, scene or other nodes remain in other objects (particularly singletons and global variables).

Whorehouse answered 8/10, 2013 at 16:52 Comment(3)
Thanks for the quick answer! Indeed, no leaks are present (apart from a couple of 64byte leaks produces by the physics module), there are no retain cycles or strong references containing the scene or the SKView object, still, the normal behavior would be, if I go into creating the scene with 180MB of allocated memory, go up to 230MB during the usage f the scene, after I quit, it should drop back to a value very close to 180MB - this of course without any caching. Do you think that this behavior is justified by Sprite Kit's caching?Lillis
possibly, it needs explicit testing to verify. Maybe SK keeps the cached textures for later reuse and frees them only on memory warning. Try sending a mem warning in simulator and see if memory then drops.Whorehouse
hmm, interesting idea, but no, it doesn't drop even after a memory warning.Lillis
F
9

After fighting this for a couple days, the key was in fact: [sceneView presentScene:nil]; Or for Swift: sceneView.presentScene(nil)

which can be done in viewDidDisappear. Without this, your view will hang on to the scene for dear life, even after being dismissed, and continue to chew memory.

Ferris answered 12/3, 2015 at 18:42 Comment(1)
I have done this but it makes the screen blank even though I present the next scene after setting sceneView.presentScene(nil). Any ideas to fix this?Beckner
P
5

Swift 3:

In my personal experience I've solved with the help of the Xcode instruments, first of all with the activity monitor that showed me immediatly the huge memory increase, than with allocation and leaks.

But there is also an useful way, the debug console with

deinit {
       print("\n THE SCENE \(type(of:self)) WAS REMOVED FROM MEMORY (DEINIT) \n")
}

This is another help to see if deinit was called everytime you want to remove a scene.

You do not ever have strong references to the scene or parent in your classes, if you have anyone you must trasform it to weak like for example:

weak var parentScene:SKScene?

Same thing for protocol, you could declare it to weak like this example using the property class:

protocol ResumeBtnSelectorDelegate: class {
    func didPressResumeBtn(resumeBtn:SKSpriteNode)
}

weak var resumeBtnDelegate:ResumeBtnSelectorDelegate?

ARC do all the work we needed but, if you think you've forgot to write correctly some property (initiliazation, blocks..) I've used also some functions like this for my debug phases :

func cleanScene() {
    if let s = self.view?.scene {
        NotificationCenter.default.removeObserver(self)
        self.children
            .forEach {
                $0.removeAllActions()
                $0.removeAllChildren()
                $0.removeFromParent()
        }
        s.removeAllActions()
        s.removeAllChildren()
        s.removeFromParent()
    }
}

override func willMove(from view: SKView) {
    cleanScene()
    self.removeAllActions()
    self.removeAllChildren()
}
Precincts answered 3/9, 2016 at 11:32 Comment(0)
B
1

I had a similar problem like you @user2857148. I would present a VC with:

[self presentViewController:myViewController animated:YES completion:nil];

In the @implementation myViewController I had :

- (void)viewDidLayoutSubviews
{
    // Configure the view.
    SKView * skView = (SKView *)self.view;
    skView.showsFPS = YES;
    skView.showsNodeCount = YES;
    self.ballonMGScene = [[MBDBallonMiniGame alloc] initWithSize:skView.bounds.size andBallonImageNames:self.ballonObjectsArray];
    self.ballonMGScene.parentVC = self;
    self.ballonMGScene.scaleMode = SKSceneScaleModeAspectFill;
    self.ballonMGScene.physicsWorld.gravity = CGVectorMake(0, 0);
    // Present the scene.
    [skView presentScene:self.ballonMGScene];
} 

The problem was in :

self.ballonMGScene.parentVC = self;

since in :

@interface MBDBallonMiniGame : SKScene <SKPhysicsContactDelegate>

parentVC was declared with strong :

@property (nonatomic,strong) WBMMiniGameVCTemplate *parentVC;

Solution 1:

and changing it to :

@property (nonatomic,weak) WBMMiniGameVCTemplate *parentVC;

solved the problem for me.

Explanation: The reference to the parentVC (myViewController) which was a UIViewController has been stored somewhere. Since this VC had a strong reference to the SKScene , it was stored with it. I even had console output from this SKScene like it was still active. My best quess on why this happened to me was that I have most strong pointers.

Solution 2:

In my myViewController under :

- (void)viewDidDisappear:(BOOL)animated

I called :

self.ballonMGScene.parentVC = nil;

On leaving the current VC (myViewController) I set the pointer to nil, removing the memory and everything along with it.

These 2 solutions worked for me. I tested it with the debugger. The memory consumption went up and down correctly.

Hope this helps in understanding the problem and solutions.

Bonney answered 29/4, 2014 at 11:56 Comment(0)
P
0

Try to retain the sceneView before removing the scene.

-(void)dealloc
{
[sceneView presentScene:nil];
[sceneView release];
[super dealloc];
}
Paleethnology answered 10/10, 2014 at 16:13 Comment(1)
Performance was dropping for me every time I went back to the stage. I started with Apple's default template for the SpriteKit. In Swift, I've just added this function and performance seems back to normal, override func viewWillDisappear(animated: Bool) { if let skView = self.view as? SKView { skView.presentScene(nil) } }Sosthina
T
0

It took some steps, but i completely solved my problem:

1) In My ViewControl, i created a method to force destroy all children:

-(void)destroyAllSub:(SKNode*)node
{
    if(node == nil) return;
    if(![node isKindOfClass:[SKNode class]]) return;

    [node removeAllActions];
    for (SKNode *subNode in node.children) {
        [self destroyAllSub:subNode];
    }
    [node removeAllChildren];
}

2) Since I had created a strong protocol in my Scene and referenced it in my ViewControl and my scene was strong also, I destroyed all references as following:

[self.mainScene.view presentScene:nil]; //mainScene: the name of the Scene pointer
self.mainScene.myProt = nil; //myProt: The name of the strong protocol

@autoreleasepool {
    [self destroyAllSub:self.mainScene];
    self.mainScene = nil;
}
Theis answered 16/11, 2015 at 1:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.