【问题标题】:What are the likely causes of random crashes with cocos2d and how do I track them down?cocos2d 随机崩溃的可能原因是什么,我如何追踪它们?
【发布时间】:2011-11-08 11:07:33
【问题描述】:

我有一个街机风格的 iPhone 游戏,它是用 Cocos2d-iPhone 构建的,运行良好,只是在玩了不可预知的游戏后,它会无缘无故地随机崩溃。有时您可以毫无问题地玩多个关卡,而其他关卡会在几秒钟后崩溃。 XCode 中报告了一些常见的行,通常是 EXC_BAD_ACCESS ,这表明内存问题,但对象都是带有额外保留的自动释放,并且崩溃非常不一致。似乎合适的一件事是,随着更多事件的发生,崩溃的可能性增加,这再次表明存在一些内存问题,但随后没有发出应用程序内存警告。

一些示例崩溃行是:

if(gameGlobals.gameState == GAME_STATE_PAUSED) {...}
if(![hearts isKindOfClass:[CCSprite class]]) {...}

但是这两行都有全局对象,这些对象在崩溃之前很长一段时间内都很有趣。

所以,我的问题实际上是我该如何追查问题,是否有任何可能的罪魁祸首?

谢谢。

更新

我一直在做一些侦探工作,我至少有一些一致性。我认为发生了两件事:

1) 是用于检测与删除像素数据的对象的像素完美碰撞的数学运算正在消耗 CPU。

2) 我有一些僵尸精灵,其他的精灵在我没有意识到的情况下被释放。

当我添加一个子弹精灵时,我使用:

CCSprite *bullet = [CCSprite spriteWithFile:@"bullet.png"];
bullet.tag = BULLET_TAG_GOODY;
bullet.position = ccp(player.position.x,50);
[self addChild:bullet];

[bullet runAction:[CCSequence actions:
[CCMoveTo actionWithDuration:2 position:ccp(player.position.x,320)],
                                   [CCCallFuncN actionWithTarget:self selector:@selector(bulletMoveFinished:)],
                                   nil]];
[bullets addObject:bullet];

然后我循环遍历子弹数组。目前它崩溃说我正在尝试访问一个已释放的实例。

那么,1) 我该如何设置,这样我的精灵就不会被释放?从 CCSprite 中删除自动释放是个好主意吗? 2) 检测子弹与太空侵略者风格基地碰撞的最佳方法是什么?

目前:

if([self overlapBases: bullet.position.x :bullet.position.y :true]) {...}

-(bool)overlapBases :(GLfloat)x :(GLfloat)y :(bool)up {
//   NSLog(@"overlapping - %f,%f",x,y);
 if(y > 20 && y < 100) {
     int bn = -1;
     if(x > 28 && x < 118) {
         bn = 0;
     } 

     if(x>140 && x<230 ) {
         bn = 1;
     }

     if(x>254 && x<343 ) {
         bn = 2;
     }

     if(x>365 && x<453 ) {
         bn = 3;
     }

     if(bn> -1) {
     NSLog(@"overlapping - %f,%f",x,y);
//     for (int ix = 0; ix < NUM_BASES; ix++) {

         if (overLap(x, 2, aspeaker[bn].position.x, BASE_WIDTH * aspeaker[bn].scale)) {
             if (overLap(y, 4, aspeaker[bn].position.y, BASE_HEIGHT * aspeaker[bn].scale)) {
                 NSLog(@"ix: %i, x: %f, y: %f",bn,x,y);
                 return [self fineCollision:bn :x :y :up];
             }
         }
     }
}
return false;    
}


-(bool)fineCollision :(GLuint)baseNum :(GLfloat)x :(GLfloat)y :(bool)up {
 //convert  bullet x and y passed in to this base coords
 //check if solid
 //if so, remove bits in gfx and array, return true
 //else return false;

 GLfloat bullx = (x - aspeaker[baseNum].position.x + BASE_WIDTH) / 2.0;
 GLfloat bully = (y - aspeaker[baseNum].position.y + BASE_HEIGHT) / 2.0;
 GLint testx = (GLint)bullx;
 if ((testx < 0) | (testx >= BASE_WIDTH)) {
 return false;
 }

 GLuint testy;  
 bool hit = false;

for (int iy = -2; iy < 2; iy++) {

testy = (GLint)(bully - iy);                            
if ((testy >= 0) & (testy < BASE_HEIGHT)) {

if (baseShape[baseNum][15 - testy][testx] > 0) {


 if(showrm == YES) {
     CCSprite *maskSprite = [CCSprite spriteWithFile:@"bullet-mask.png"];

     [maskSprite setBlendFunc: (ccBlendFunc) {GL_ZERO, GL_ONE_MINUS_SRC_ALPHA}];

     maskSprite.position = ccp( x , y-(40+22-5) );

     NSLog(@"sprite: %f",maskSprite.position.x);

     NSLog(@"render mask: %f",rm.frameInterval);
     [rm addSpriteMask:maskSprite];





     [rm drawCurrent];


 }


 [self remove:testx :testy :baseNum :up];

 GLuint seed = rand()%64;
 if (seed & 1) {

 [self remove:testx - 1:testy - 1 :baseNum :up];
 }
 if (seed & 2) {
 [self remove:testx - 1:testy :baseNum :up];
 }
 if (seed & 4) {
 [self remove:testx - 1:testy + 1:baseNum :up];
 }
 if (seed & 8) {
 [self remove:testx + 1:testy - 1:baseNum :up];
 }
 if (seed & 16) {
 [self remove:testx + 1:testy :baseNum :up];
 }              
 if (seed & 32) {
 [self remove:testx + 1:testy + 1:baseNum :up];                 
 }

 hit = true;                
 }
 }
 }

 return hit;
 }



 - (void)remove:(GLint)offX :(GLint)offY :(GLuint)baseNum :(bool)up {
 if ((offX < 0) | (offX >= BASE_WIDTH)) {
 return;
 }

 if ((offY < 0) | (offY >= BASE_HEIGHT)) {
 return;
 }  
 baseShape[baseNum][15 - offY][offX] = 0;


 }

bool overLap(GLfloat x1, GLfloat w1, GLfloat x2, GLfloat w2) {

GLfloat left1, left2;
GLfloat right1, right2;

GLfloat halfWidth1 = w1 * 0.5f;
GLfloat halfWidth2 = w2 * 0.5f;

left1 = x1 - halfWidth1;
left2 = x2 - halfWidth2;
right1 = x1 + halfWidth1;
right2 = x2 + halfWidth2;

if (left1 > right2) {
    return false;
}

if (right1 < left2) {
    return false;
}

return true;
}

对于检测和渲染蒙版,请考虑使用大量精灵及其混合模式来进行消耗。

谢谢。

再次更新:)

好的,我找到了一些僵尸!

Address Category    Event Type  RefCt   Timestamp   Size    Responsible Library Responsible Caller

0   0x13a00e90  CCSprite    Malloc  1   00:32.974.212   432 SpacedInvaders  +[CCSprite spriteWithFile:]

1   0x13a00e90  CCSprite    Autorelease <null>  00:32.974.235   0   SpacedInvaders  +[CCSprite spriteWithFile:]

2   0x13a00e90  CCSprite    Retain  2   00:32.974.546   0   SpacedInvaders  -[CCArray insertObject:atIndex:]

3   0x13a00e90  CCSprite    Retain  3   00:32.974.629   0   SpacedInvaders  -[CCActionManager addAction:target:paused:]

4   0x13a00e90  CCSprite    Retain  4   00:32.974.634   0   SpacedInvaders  -[CCArray addObject:]

5   0x13a00e90  CCSprite    Release 3   00:32.986.279   0   QuartzCore  CA::Display::DisplayLink::dispatch(unsigned long long, unsigned long long)

6   0x13a00e90  CCSprite    Release 2   00:33.074.889   0   SpacedInvaders  -[CCArray removeObject:]

7   0x13a00e90  CCSprite    Release 1   00:33.074.915   0   SpacedInvaders  -[CCActionManager deleteHashElement:]

8   0x13a00e90  CCSprite    Release 0   00:33.074.918   0   SpacedInvaders  -[CCArray removeObject:]

9   0x13a00e90  CCSprite    Zombie  -1  00:33.074.939   0   SpacedInvaders  -[GameLayer update:]

那么这里发生了什么?我看到保留计数太低了,但我该如何避免这种情况。我假设我在做一些愚蠢的事情。

谢谢。

【问题讨论】:

  • 好的,到目前为止感谢 cmets。
  • 好的,感谢 cmets 到目前为止(再次)。需要明确的是,自动发布是 cocos2d 核心代码的一部分。僵尸已启用,但仪器中没有。没有明显的泄漏或其他(我可以看到)。
  • 僵尸不是为了工具,而是将 EXC_BAD_ACCESS 崩溃更改为“xxxx 发送到已释放的 yyyy 实例”的消息,帮助您查明问题。也许这个调试速成课程可能会有所帮助:learn-cocos2d.com/2011/10/xcode-4-debugging-crashcourse

标签: iphone objective-c cocos2d-iphone


【解决方案1】:

这条线可以崩溃的唯一方法

if(gameGlobals.gameState == GAME_STATE_PAUSED) {...}

如果gameGlobals 是一个已经被释放的Objective-C 对象。

如果 gameGlobals 是 C struct,您将看不到 EXC_BAD_ACCESS,因为它不是指针。

如果gameGlobalsnil 对象,您将看不到EXC_BAD_ACCESS,因为可以向nil 发送消息。

您的声明“这些对象都是带有额外保留的自动释放”给我敲响了警钟,因为它表明您并不真正了解内存管理规则。应该在应用程序的整个生命周期中存在的全局对象不需要自动释放。你应该在开始时分配它,就是这样。或者,考虑使您的应用程序委托的全局对象属性。

如果您仍然无法弄清楚,ade 的答案的第一部分是要走的路(NSZombie),尽管第二部分可能无济于事。我怀疑你的对象是否会为零。

【讨论】:

  • GameGlobals 是一个 Objective C 类。它通过一个简单的 alloc-init 实例化为一个全局实例。这个对象不是自动释放的,只有 Cocos2d 对象是默认情况。
  • @Martin:请您出示您的声明、定义和初始化。
【解决方案2】:

对我来说,这听起来确实像是记忆问题。如果这是一个巨大的内存泄漏,您之前也不一定会收到内存警告。

您是否尝试过使用 XCode 分析您的游戏是否存在漏洞?如果是内存问题,您会在分析期间崩溃时看到它。

【讨论】:

    【解决方案3】:

    “对象都是自动释放的”听起来可能是原因。自动释放在 UI“出现”时生效(控制从当前 UI 事件处理逻辑返回),如果您希望在此之后继续使用自动释放变量,它可能已经“噗”了(虽然通常您可以在之后使用它一段时间,直到重新使用存储空间)。

    您是否运行过分析器或任何其他工具?

    【讨论】:

      【解决方案4】:

      如果我对您的理解正确,那么“带有额外保留的自动释放”听起来像是一个问题(除非您还有额外的释放),这可能会导致泄漏导致内存使用量增加,从而导致其他对象被释放,然后您随后尝试访问。

      尝试启用 nszombies:How to enable NSZombie in Xcode?

      还可以使用 nslog 语句来跟踪您期望对象不为空的情况。

      如果您知道某些情况下会导致崩溃,那么也许您可以在此之前在 NULL 检查的 else 中放置一个断点:

      if(gameGlobals.gameState != NULL) {
          if(gameGlobals.gameState == GAME_STATE_PAUSED) {...}
      } else {
          //break or nslog here
      }
      

      【讨论】:

        【解决方案5】:

        首先要意识到可能EXC_BAD_ACCESS 出现的代码没有问题。但是,值得调查该特定对象以防万一。追求的第一个途径是其中一个对象已被释放,因为它没有被充分保留。所以,是的,绝对按照这里其他人的建议启用 Zombies。

        释放的对象可能不会立即释放。这就是为什么EXC_BAD_ACCESS会在奇怪的时间出现的原因。仅当不再需要它们正在使用的内存块时,才会释放对象。其他对象当然会使用相同的块。因此,当该块符合释放条件时,您作为开发人员无法控制。这是由运行时处理的。

        最好的办法是先运行 Analyzer,然后运行 ​​Instruments,使用 Leaks 仪器进行分析。

        对这一切都有很好的解释,并在 Lou Franco 的网站上提供了关于追踪EXC_BAD_ACCESSerrors 的逐点建议:

        Understanding EXC_BAD_ACCESS

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多