0

我很确定我以某种方式犯了一个愚蠢的错误,但我似乎无法修复它。我得到了一个 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]时retain count减1,MyLayer::release被调用一次(通过[super dealloc] - 删除CCNode中的所有children),大家开心。

但是,一旦我完成了整个游戏(杀死设备上的应用程序)并调用了 CCDirector::end ,整个事情就中断了,因为事实上,它尝试释放 _myLayer 两次 - 一次使用显式调用,一次通过释放孩子。

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

我尝试一起废弃 [_myLayer 版本]。在这种情况下,_myLayer 在运行时根本不会被释放,但在关机期间一切正常。所以这里有点一致,但这并没有真正帮助我。

4

2 回答 2

2

首先:retainCount没用

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

_myLayer = [MyLayer node];

它相当于:

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

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

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

[self addChild:_myLayer];

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

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

[_myLayer release];

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

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

我可以释放整个场景

如果您的意思是您将release消息发送到现场,那是错误的。这又会过度释放场景。当你调用 replaceScene 或类似方法时,Cocos2d 会清理场景。场景本身通常也是自动释放的,尤其是在通过nodeorscene类方法创建时。

如果这不是您正在做的并且该层没有释放,那么请检查您是否有保留周期。或者您可能只是希望在场景之前释放层?这不一定是这个顺序,自动释放也不少。

您可以通过让两个(或更多)兄弟节点相互保持(即 A 层保留 B 层,B 层保留 A 层)或兄弟节点保留其父节点(例如 _myLayer 持有保留引用)轻松创建保留循环到场景(顺便说一句,这与通过 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 可以节省大量时间,即使您真的非常非常非常非常非常想学习这一点,您最好真的非常非常非常几乎完全对过去 Objective-C 开发时代的内存管理如何工作感兴趣。你知道,就像我奶奶还在为第一部 iPhone 编写代码时一样!:)

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

这就是为什么我对使用 ARC 很鲁莽,因为不这样做是不负责任的。国际海事组织。

于 2013-10-30T16:03:19.037 回答
1

在 dealloc 中,不要释放 myLayer,它已经为您保留(当您 addChild 时)。相反,我倾向于'removeAllChildrenWithCleanup:YES',这将释放 myLayer。这里发生的事情(我怀疑)最终,导演正试图按照我所说的去做,但你已经发布了 myLayer。所以它的孩子数组中有一个僵尸。

于 2013-10-30T14:07:05.100 回答