Cocos-2d iOs - cclayer的不同内存释放行为

时间:2013-10-30 13:36:47

标签: ios objective-c memory-management cocos2d-iphone

我很确定我以某种方式犯了一个愚蠢的错误,但我似乎无法修复它。我有一个CCScene的子类,它又有一个cclayer的子类作为一个层,大致如下所示:

@interface MyLayer : CCLayer {
}

// some methods

@end

@interface MyScene : CCScene {

    MyLayer *_myLayer;

}

// some methods

@end

在构建时,我会执行以下操作:

-(id) init {
    if (self = [super init]) {
        _myLayer = [MyLayer node];
        [self addChild:_myLayer];

        // more stuff
    }
}

我需要_myLayer中的引用,因为我需要与图层进行交互。但是,这使我的保留计数为2(一次在_myLayer中,一次作为场景的子节点)。到目前为止没问题 - 至少我理解它。然而,这也意味着,我必须释放它。所以,MyScene的dealloc看起来像这样:

-(void) dealloc {

    [_myLayer release];
    _myLayer = nil;

    [super dealloc];

}

如果我现在在运行时释放场景,一切正常。我完成了发布过程,这一切都很好,我可以释放整个场景,包括没有问题的层。 MyScene :: release被调用一次,在主动调用[_myLayer release]时将保留计数降低一,MyLayer :: release被调用一次(通过[super dealloc] - 删除CCNode中的所有子项),大家都很高兴。

然而,只要整个游戏(杀死设备上的应用程序)和CCDirector :: end被调用,整个事情就会中断,因为事实上,它试图释放_myLayer两次 - 一次是明确的通过释放孩子来打电话。

现在我可以理解,如果在两种情况下都是相同的话我犯了一些错误 - 但事实并非如此。一旦它按预期工作 - 在第一种情况下将保留计数降低一,然后再次释放它,一旦它的工作方式不同。我不知道为什么会这样。

我试图完全废弃[_myLayer发布]。在这种情况下,_myLayer在运行时根本不会被释放,但在关机期间一切正常。所以这里有点一致,但这对我没有帮助。

2 个答案:

答案 0 :(得分:2)

首先:retainCount is useless

这里返回一个自动释放的MyLayer实例:

_myLayer = [MyLayer node];

相当于:

_myLayer = [[[MyLayer alloc] init] autorelease];

如果你在没有其他代码的情况下离开它,_myLayer将在init方法返回后的某个时间成为悬空指针。绝对是下次清除自动释放池时,如果我记得在cocos2d中正确发生了帧。在ARC下,ivar本身默认为强引用,因此该层将被保留,并且不会像您期望的那样解除分配。因此,如果您不喜欢自动释放及其行为,请使用ARC。 ;)

然后将图层添加为子图层,将其放入数组中,这意味着它会保留图层:

[self addChild:_myLayer];

因此,只要图层仍然是场景的子图层,它就不会解除分配。

现在,就像你之前的许多人一样,你正在寻找错误的地方来解决层不释放的问题。通过在dealloc中执行此操作,您添加了一个无关的版本:

[_myLayer release];

现在这个工作正常,因为实际问题是图层没有释放,你强迫它在这里发布。但是一段时间后,场景的子阵列将被释放,它会向每个对象发送释放,然后由于过度释放图层而导致崩溃。

因此,您应该追踪的实际问题是该层未解除分配的原因。在这里,我感觉到更多的问题:

  

我可以释放整个场景

如果你的意思是你在向场景发送release消息,那就错了。而这又会过度释放场景。调用replaceScene或类似方法时,Cocos2d将清除场景。场景本身通常也是自动释放的,当然是通过nodescene类方法创建的。

如果这不是您正在做的事情并且该图层没有发布,那么请检查您是否有保留周期。或者您可能只是期望图层在场景之前解除分配?这不一定必须按此顺序排列,自动释放不会少。

您可以通过让两个(或多个)兄弟节点相互保持(即图层A保留图层B和图层B保留图层A)或通过保留其父图像的兄弟节点轻松创建保留周期,例如_myLayer holding保留对场景的引用(btw与通过self.parent访问它相同)。

现在我说要使用ARC,因为它会使所有这些问题几乎立即消失,除了保留周期。但对于保留周期,它提供了一种非常简单有效的治疗方法:将弱参考归零。

例如,您可以在界面中将图层引用声明为弱,并且不再担心它会保留图层:

 __weak MyLayer *_myLayer;

此外,当释放图层时,_myLayer将自动设置为nil。这种情况发生在图层没有更多强引用的情况下,而不是像autorelease那样在某个时候发生。

积极的副作用是您现在可以安全地执行以下操作:

@interface LayerA : CCLayer
@property (weak) LayerB* layerB;
@end

@interface LayerB : CCLayer
@property (weak) LayerA* layerA;
@end

在MRC下,如果您相应地分配图层并且在 dealloc方法之前不将它们设置为nil ,则会创建一个保留周期。关于保留周期的棘手问题在于它们无法在dealloc方法中解析,因为根据定义,参与保留周期的所有对象都不会被解除分配。在cocos2d中这样做的典型地方是清理方法。

但现在使用ARC绝对没问题。层中的任何一个或两个层都将解除分配,因为另一层中的引用不保留(弱),并且当任一层被解除分配时,引用被设置为nil,因此不会发生任何崩溃。

我一直在使用ARC专门开发两年。我再也没有回头。我对内存管理的错误配额几乎为零,这很荒谬。关于我偶尔要研究的唯一一件事是,当我不期望它时,弱参考是零。通常情况下,当我错误地认为对象在某处有一个强引用时,但不是。

使用ARC是如此巨大的节省时间,即使你真的真的非常非常想要学习这一点,你最好真的真的几乎完全关注内存管理过去在目标中的工作方式。 C开发。你知道,就像我奶奶还在为第一部iPhone编写代码一样! :)

PS:在使用ARC时你放弃了对内存管理的控制是一个神话。即使在很短的时间内,您也可以采取许多巧妙的技巧来获得控制权。主要通过使用桥梁铸造。因此,即使ARC可以花费更多的周期并且可能在紧密的循环中加起来,您仍然可以使用MRC手动优化代码(如果必须,您可能永远不会有)。这超出了这个答案的范围(我已经走得太远了)但这些选项确实存在,但人们几乎不需要练习它们。

这就是为什么我会因使用ARC而烦恼,因为不这样做是边缘不负责任的。 IMO。

答案 1 :(得分:1)

在dealloc中,不要释放myLayer,它已经为你举行了(当你addChild时)。相反,我倾向于'removeAllChildrenWithCleanup:YES',这将释放myLayer。这里发生的事情(我怀疑)是,最后,导演正试图做我说的但你已经释放了myLayer。所以它的孩子们就会变成一个僵尸。