Cocos2d and iOS: how to deal with dealloc methods of static CCScene - ios

I am using some static instance of a GameScene subclass of CCScene. Calling from GameScene
[[CCDirector sharedDirector] replaceScene:[MainMenuScene scene]];
doesn't trigger the dealloc method of the GameScene.
The method is called once I load again the scene (and a new GameScene is created):
+(id) sceneWithId:(int)sceneId
{
CCScene* scene = [CCScene node];
GameScene* gameScene = [[self alloc] initWithId:sceneId];
[scene addChild:gameScene z:0 tag:GameSceneLayerTagGame];
return scene;
}
-(id) initWithId:(int)sceneId
{
CCLOG(#"scene With id");
if ((self = [super init]))
{
instanceOfGameScene = self;
//ONLY NOW the previous object becomes unreferenced and the memory management system is allowed to deallocate it
I wanted to understand if there is a way of forcing the dealloc method to be called every time I replace a (static) scene and not only when the memory gets "freed".
Or, if I should instead, write some cleanups methods that stops ongoing processes that I don't want to effect the MainMenuScene (e.g. I have put a stop background music method call in the dealloc method of GameScene but as also the background music is in a static class -and is not added to GameScene as child- then it keeps playing once back in the MainMenuScene).
My quick fix proposal hence is to do something like this:
[self stopAllStuffInOtherStaticClassesThatAreRelatedOnlyToGameScene];
[[CCDirector sharedDirector] replaceScene:[MainMenuScene scene]];
Is this a good approach?
EDIT: when is sensible to add this code in the dealloc method?
[self removeAllChildrenWithCleanup:TRUE];

I smell bad practice. Never, ever keep a static instance of a node around. Especially not outside the scene hierarchy. It just breaks cocos2d's way of handling memory management.
If you need to preserve state, save this state to a separate class but by all means let the scene go when you change scenes. Then restore the state when the scene inits again.
You can not force the dealloc method. Wanting to do so is a sign of code smell. You can however override the -(void) cleanup method to run de-initialization code before dealloc and after the node has been removed as child.
EDIT: when is sensible to add this code in the dealloc method?
[self removeAllChildrenWithCleanup:TRUE];
Never. Ever.
Again, if you find you need to do this, there's a terrible bug somewhere. Cocos2D will have removed the child nodes by this time. In fact, it does so during the cleanup method, one way to cause cocos2d not to remove a node's children is when you override cleanup and not call [super cleanup].
Or perhaps when you keep it as a static instance, so it'll never actually call the cleanup method. And even worse would then continue to run scheduled updates and actions.

You trouble is that you don't release your game scene. Create it as
GameScene* gameScene = [[[self alloc] initWithId:sceneId] autorelease];
and it will be deallocated right.
Anyway, I don't see any need to create game scene as a child of other scene, but maybe i don't know something about your project.
And I cannot understand, what do you mean under "static CCScene"

Using 'Instruments' under Developer Tools, I was able to make some progress on this matter:
When the scene is created it should follow a few simple rules to keep the memory low:
Allocate the sprites from smallest to largest
Make sure to call [self unscheduleAllSelectors]; at the right times
Override the -(void)dealloc method with the following code if you want to see significant memory improvements in your case, as this will hit every time the CCScene is loaded:
// Clean up memory allocations from sprites
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
[[CCDirector sharedDirector] purgeCachedData];
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFrames];
[CCSpriteFrameCache purgeSharedSpriteFrameCache];

Related

Clearing a SKScene to start a new game

After my game ends I have a button called play again but when it runs it throws and error saying Attemped to add a SKNode which already has a parent. My question is can I reset or dealloc the SKScene so that it is like a fresh slate, like the app was never run?
Assuming the code is being executed in your scene I think what you want is this..
MyScene *newScene = [[MyScene alloc]initWithSize:self.size];
[self.view presentScene:newScene];
Where MyScene is SKScene subclass.
Hopefully that is what you were looking for.
You can use [self removeAllChildren]; in your SKScene to remove all children nodes.
Other objects like arrays, strings, etc... you will have to deal with on a one to one basis.
You can check if a node already has a parent before adding by doing this:
if(myNode.parent == nil)
[self addChild:myNode];

After Closing SKScene, Memory Remains High

I use a dispatch_once NSObject to create data pointers. So all game asset pointers are made when the main viewcontroller appears. In order to play a game, the user taps a UIButton corresponding to a particular level on a UIViewController. Let me call it LevelSelectionController. When the game is over, the user will tap a label (SKLabel). And all actions and nodes will be removed.
[self removeAllActions];
[self removeAllChildren];
[self removeFromParent];
Moreover, an SKScene subclass for a particular level delegates the task of returning the user to LevelSelectionController to the viewcontroller presenting game SKView as follows.
- (void)closeScene {
SKView *spriteView = [[SKView alloc] init];
[spriteView presentScene:nil];
[self.navigationController popViewControllerAnimated:YES];
}
The only issue that I have is that the memory remains high when the user leaves the game scene (SKScene). The game requires a lot of assets. So when the game starts, the memory usage will jump to 200 MB. When the user returns to the original level selection view controller, the game simulator is still consuming 200 MB according to Activity Monitor. When the user enters a different level, the memory usage will jump by another 10 MB. So how can I release the memory for the last game once the user leaves SKScene?
I'm using ARC. The Xcode version is 5.1. The development target is iOS 7.1.
Thank you for your help.
-- Edit 1 --
I'm silly. I know what the problem is. When I close the scene, I'm creating a new SKView, which I then set it to nil to get of out the current scene. It works. But that should not be the way of doing it. Instead, I need to set the current SKView to a variable before presenting it. When I close the scene, I need to set that variable to nil. Hmm... I wasn't thinking.
-- Edit 2 --
There's little change when the current scene is presented with nil. Removing it from removeFromSuperview doesn't do much.
It has been noticed by a few people on SO that an SKScene gets deallocated when the containing SKView is removed from it's superview.
Have a look at these questions and their answers:
Deallocate SKScene after transition to another SKScene in SpriteKit
iOS 7 Sprite Kit freeing up memory
Also, try modifying the closeScene method like so:
- (void)closeScene {
SKView *spriteView = (SKView*)self.view;
[spriteView presentScene:nil];
[self.navigationController popViewControllerAnimated:YES];
}
Put NSLog() to SKScene's dealloc method to be sure that it's deallocating.
Also, resources may be not released just after reference count of your scene reaches 0. Due to internal optimizations assets may stay in memory until receiving Memory warning signal.

Cocos-2d iOs - different memory release behaviour of cclayer

I'm pretty sure I somehow made a stupid mistake but I seem to be unable to fix it. I got a subclass of CCScene, which in turn has a subclass of cclayer as a layer, roughly looking like this:
#interface MyLayer : CCLayer {
}
// some methods
#end
#interface MyScene : CCScene {
MyLayer *_myLayer;
}
// some methods
#end
On constructing the I do the following:
-(id) init {
if (self = [super init]) {
_myLayer = [MyLayer node];
[self addChild:_myLayer];
// more stuff
}
}
I need the reference in _myLayer because I need to interact with the layer. However, this leaves me with a retain count of 2 (once in _myLayer, once as a child node of the scene). No problem so far - at least as I'm understanding it. This, however, also means, I have to release it. So, the dealloc of MyScene looks like this:
-(void) dealloc {
[_myLayer release];
_myLayer = nil;
[super dealloc];
}
If I now release the Scene during runtime, everything works fine. I folled the process of releases and it's all good, I can release the whole scene including the layer without problems. MyScene::release is called once, lowers the retain count by one when actively calling [_myLayer release], MyLayer::release is called once (through the [super dealloc] - delete all children in CCNode), everyone is happy.
However, as soon as I quite the whole game (kill the app on the device) and CCDirector::end is called, the whole thing breaks, because in fact, it tries to release the _myLayer twice - once with the explicit call, once through releasing the children.
Now I could understand that I made some kind of mistake if it would be the same in both cases - but it isn't. Once it works as expected - in the first case lowering the retain count by one, then again, releasing it, once it works differently. And I have no clue why that is the case.
I tried scrapping the [_myLayer release] alltogether. In that case _myLayer doesn't get released at all during runtime but everything works out fine during shutdown. So it's kinda consistent here, but that doesn't really help me.
First of all: retainCount is useless.
This here returns an autoreleased instance of MyLayer:
_myLayer = [MyLayer node];
It is the equivalent of:
_myLayer = [[[MyLayer alloc] init] autorelease];
If you were to leave it at that with no other code, the _myLayer would become a dangling pointer some time after the init method returns. Definitely the next time the autorelease pool is purged, which if I remember correctly happens ever frame in cocos2d. Under ARC, the ivar by itself defaults to being a strong reference, so the layer would be retained and would not deallocate as you would expect. So if you don't like autorelease and how it behaves, use ARC. ;)
Then you add the layer as child, which puts it in an array, which means this retains the layer:
[self addChild:_myLayer];
So as long as the layer remains a child of the scene, it will not deallocate.
And now, like so many before you, you were looking at the wrong place to fix the problem of the layer not releasing. By doing this in dealloc you add an extraneous release:
[_myLayer release];
Now this works fine for the moment because the actual problem is the layer not releasing, and you force it to be released here. However some time later the scene's children array will be released, which sends release to each object, which then causes the crash due to over-releasing the layer.
Hence the actual problem that you should be tracking down is why the layer doesn't deallocate. And here I sense more problems:
I can release the whole scene
If by that you mean you were sending the release message to the scene, then that's wrong. And again this would over-release the scene. Cocos2d will clean out the scene when you call replaceScene or similar methods. The scene itself is typically autoreleased as well, definitely when created through the node or scene class methods.
If that's not what you're doing and the layer doesn't release, then check if maybe you have a retain cycle. Or perhaps you're simply expecting the layers to deallocate before the scene? That doesn't necessarily have to be in this order, with autorelease no less.
You can easily create a retain cycle by having two (or more) sibling nodes holding on to each other (ie layer A retains layer B and layer B retains layer A) or by a sibling retaining its parent, for example _myLayer holding a retained reference to the scene (which btw is the same as accessing it via self.parent).
Now I'm saying to use ARC because it makes all those problems go away almost instantly, except for retain cycles. But for retain cycles it provides a very simple and effective cure: zeroing weak references.
For example you could declare the layer reference as weak in the interface and no longer worry about it retaining the layer:
__weak MyLayer *_myLayer;
Moreover when the layer is released, _myLayer will automatically be set to nil. And this happens the instance the layer has no more strong references, not at some later time as is the case with autorelease.
The positive side effect is that you can now safely do the following:
#interface LayerA : CCLayer
#property (weak) LayerB* layerB;
#end
#interface LayerB : CCLayer
#property (weak) LayerA* layerA;
#end
Under MRC this would create a retain cycle if you assign the layers accordingly and don't set them to nil before the dealloc method. The tricky thing about retain cycles is that they can not be resolved within the dealloc method since by definition all objects participating in a retain cycle won't be deallocating. A typical place to do so in cocos2d is the cleanup method.
But now with ARC there is absolutely no problem. Either or both layers will deallocate because the reference in the other layer is not retaining (weak) and when either of the layers is deallocated the reference is set to nil, so there won't be any crashes.
I have been developing exclusively with ARC for two years. I never looked back. My quota of errors relating to memory management went down to almost zero, it's ridiculous. About the only thing I occasionally have to look into is when a weak reference is nil when I don't expect it to be. Usually that's when I incorrectly assume the object has a strong reference somewhere, but doesn't.
Using ARC is such a huge timesaver that even if you really really really really absolutely badly want to learn this, you better be really really really really almost exclusively interested in how memory management used to work back in the old days of Objective-C development. You know, like when my grandma was still writing code for the very first iPhone! :)
PS: it's a myth that you give up control over memory management when using ARC. There's a lot of neat tricks you can do to gain control back, even for a short time. Mainly by using bridge casting. So even if ARC can take a couple more cycles and that may add up in a tight loop, you can still hand-optimize the code (if you have to; you'll probably never ever have to) with MRC. This is beyond the scope of this answer (I already went too far) but these options do exist, but one hardly ever needs to exercise them.
This is why I'm brash about using ARC because not doing so is borderline irresponsible. IMO.
In dealloc, dont release myLayer, it already being held for you (when you addChild). Instead, i tend to 'removeAllChildrenWithCleanup:YES', this will release myLayer. What happens here (i suspect) is that on end, the director is trying to do just what i said BUT you already released myLayer. So it gets a zombie in its array of children.

Animating CCSprite not working?

I am creating a sprite like this in my init method (mySprite is declared in the .h):
mySprite = [CCSprite spriteWithFile:#"Image1.png"];
[mySprite setPosition:ccp(100, 300)];
[self addChild:mySprite z:1 tag:1];
Then in my other method, I try to animate it like so but it doesn't seem to animate at all, I also know that the method that this is in is getting called because I NSLogged it. Anyway here is how I try to animate mySprite:
CCSequence *moveSequence = [CCSequence actions:[CCMoveTo actionWithDuration:5 position:ccp(120, 400)],[CCMoveTo actionWithDuration:4 position:ccp(100, 300)], nil];
[mySprite runAction:[CCRepeatForever actionWithAction:moveSequence]];
Any ideas why this might be happening?
Thanks!
At first glance this part of the code seems right, so perhaps you would need to show more of your overall program so that we can examine what happens between your init function and your other method being called.
A couple things out of the blue:
make sure to call retain on your sprite so that it does not get removed summarily until you are done with it
what is "self" exactly here, is it a cocos layer? Is the layer added to the scene properly (ie, are you seeing the sprite being displayed, even if it does not move)?
I would also look to see if anything in the scene graph might happen between the "init" call and that second method of yours, in which you execute the animation code. Is there a StopAllActions somewhere? A removeFromParent or removeAllChildren, perhaps?
Cheers

Which cocos2d scene is currently active when returning from suspended app state

The environment is an iOS device with multitasking support, like an iPhone4.
I'm in a cocos2d app with a Main Menu that leads to several Scenes.
If I switch to another app using the taskbar, then switch back, how do I programmatically tell which scene is active?
Couldn't you just check CCDirector's runningScene property in your app delegate's applicationWillEnterForeground: method? If you subclass CCScene you could just check the scene's class, otherwise you may want to add some other sort of identifier to each scene.
you could add an identifier for the CCSCene class such as int sceneID or typedef enum { mainMenuID = 0, playSceneID, helpSceneID, aboutSceneID } sceneID and then simply assign each of these in the init method of each scene... then you can retrieve it in applicationWillEnterForeground: like so:
int theSceneID = [[[CCDirector sharedDirector] runningScene] classID];
but like Zaid suggested, it is alot easier to use the tag of CCScene.

Resources