0
votes

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.

2
Don't waste your time with questions like these. Use ARC.LearnCocos2D
Assume for a second that I just like to understand what is going on in the described case. I'm usually a c++ developer - I'm very much used to having very direct control over when to release what and to be honest, autoreleases irritate me. To no end. That shouldn't necessarily stop me from using them, but using them shouldn't stop me from the desire to understand what my code does.Aerius
Point taken, if you want to learn how this works I welcome that. In all other cases where the goal is to make the issue go away and continue with development, ARC really has you skip over all those unnecessary details.LearnCocos2D
We could debate lengthily over our respective definitions of "unnecessary details" I guess... but I understand where you're coming from. I still would like to know why things are happening in the described case as they are. ;)Aerius
I thought so, I gave you a detailed answer. ;)LearnCocos2D

2 Answers

2
votes

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.

1
votes

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.