1

我为此感到羞耻,但是是的。即使我使用 ARC ,我也遇到了问题。

我的主要问题是:我的代码中有循环引用吗?

我的代码说明:

我有一个带有一系列子节点(背景、playerEntity、inputlayer)的游戏场景。问题是当我触发游戏并替换场景时,场景不会释放(我在游戏场景中添加了一个 dealloc 方法,即使我使用 ARC 不需要)。

我相信这是因为在子节点中我指的是游戏场景,而在游戏场景中我指的是它们,创建了一个循环引用。

例如,在游戏场景更新方法中,我调用以下内容:

-(void) update:(ccTime)delta
{
bool isGamePaused = [GameController sharedGameController].isGamePaused;

if(isGameOverInProgress==true){
    //Do whatever game over animation u like
}
else if(isGamePaused==false)
{
    elapsedTime+=delta;

    //Update input layer
    InputLayerButtons *inputLayer = (InputLayerButtons *) [self getChildByTag:InputLayerTag];
    [inputLayer update:delta];

    //Move background
    ParallaxMultipleBackgroundOneBatchNode* background = (ParallaxMultipleBackgroundOneBatchNode*) [self getChildByTag:BackgroundNodeTag];
    [background update:delta];

    //verify enemies bullet collision
    [self verifyPlayerBulletCollision];
    [levelSprites update:delta];

    //Update bullets position
    [bulletCache updateVisibleBullets:delta];

我知道这是一种疯狂的方法,但我想避免在子节点中安排更新,以便在 ShooterScene 类中完全控制游戏中发生的事情。我没有对 ShooterScene 类使用半单例模式,而是有一个半单例 GameController 类实例,用于在节点之间传达游戏状态。

但是在某些子节点中,我不得不引用父 ShooterScene,因为例如使用半单例 GameController 类来存储船位置的响应速度太慢(例如,在更新方法中读取位置并相应地更新船证明要慢得多而不是像我在这里那样直接更新它)。因此,我的方法并不是像我希望的那样 100% 干净(我想避免对父类的所有引用,以避免循环引用的问题,并保持对 ShooterScene 类中所有事件的控制)。

因此,例如,这是我从输入层(即InputLayerButtons)更新船舶位置的方法:

-(void) update:(ccTime)delta
{
    // Moving the ship with the thumbstick.
    ShooterScene * shooterScene = (ShooterScene *) [self parent];
    ShipEntity *ship = [shooterScene playerShip];
    delta = delta * 2.0f;

    CGPoint velocity = ccpMult(joystick.velocity, 200);
    if (velocity.x != 0 && velocity.y != 0)
    {
        ship.position = CGPointMake(ship.position.x + velocity.x * delta, ship.position.y + velocity.y * delta);
    }     
}

我认为引用父场景是可以的,并且这不会导致循环引用。但它似乎发生了,所以我对此感到困惑。

这是游戏结束方法的代码,ShooterScene 的 dealloc 方法从未被调用(而是调用 cleanup):

   [[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:4.0 scene:[MainMenuScene scene] withColor:ccc3(255, 255, 255)]];

replaceScene 中有什么我不明白的地方吗?我找不到replaceObjectAtIndex的代码,所以在这里我只发布了 replaceScene 的 Cocos2d 2.0 代码

-(void) replaceScene: (CCScene*) scene
{
    NSAssert( scene != nil, @"Argument must be non-nil");

    NSUInteger index = [scenesStack_ count];

    sendCleanupToScene_ = YES;
    [scenesStack_ replaceObjectAtIndex:index-1 withObject:scene];
    nextScene_ = scene; // nextScene_ is a weak ref
}

我试图查看是否会在其他场景中调用 dealloc 方法(比 ShooterScene 更简单,并且没有潜在的循环引用)并且它确实有效。所以我非常有信心这是由于循环引用,但我怎样才能检测到导致这种情况发生的代码呢?

4

1 回答 1

2

ARC 不会阻止保留循环。然而,使用归零弱引用会大大降低创建保留周期的机会。

作为关于 cocos2d 节点层次结构和保留周期的一般经验法则:

如果您满足以下条件,则您没有保留周期:

  • 保持对子(或孙)节点的强引用

是的,就 cocos2d 中的保留周期而言,这是唯一安全的规则!这意味着对不是节点的子节点或孙节点的节点的引用永远不应该是强(保留)引用。

如果您:

  • 保持对父(或祖父)节点的强引用
  • 保持对兄弟节点的强引用,并且兄弟节点直接或间接地保持对原始节点的强引用

如果您满足以下条件,您可能会有保留周期:

  • 保持对不是子(或孙)节点的节点的强引用,具体取决于该节点是否具有对原始节点的强引用或其他因素。

在您的情况下,这意味着通过保持对父级的强引用,您创建了一个保留周期。在 ARC 下,默认情况下每个 ivar 都是强引用。您可以做两件事来解决这个问题和任何其他潜在的保留周期:

  • 将属性更改为弱或将 ivar 更改为 __weak(需要 iOS 5.0 作为最低部署目标 - 强烈推荐)
  • 在节点的 -(void) 清理方法中将 ivar 设置为 nil

如何创建弱引用:

// weak references to parent nodes are safe:
@interface MyClass : CCNode
{
    __weak CCLayer* _gameLayer;
}
@property (nonatomic, weak) CCScene* gameScene;
@end

如何正确清理:

-(void) cleanup
{
    _strongRefToNonChildNode = nil;
    [super cleanup];
}

-(void) momCallsShesAlmostHere
{
    [self cleanupApartment];  // just kidding :)
}

在 dealloc 中进行清理,因为人们可能认为合适,但对于解决保留周期来说为时已晚。因为如果您一个保留周期,您显然无法在 dealloc 中解决它,因为保留周期首先会阻止对象释放。

于 2012-10-16T13:16:55.490 回答