我有一款使用Cocos2d-iPhone构建的街机风格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:]
那么这里发生了什么?我发现保留计数太低了,但我怎么避免这种情况。我认为我正在做一些愚蠢的事情。
感谢。
答案 0 :(得分:1)
此行可能崩溃的唯一方法
if(gameGlobals.gameState == GAME_STATE_PAUSED) {...}
是gameGlobals
是否已经解除分配的Objective-C对象。
如果gameGlobals
是C struct
,则您不会看到EXC_BAD_ACCESS
,因为它不是指针。
如果gameGlobals
是nil
对象,则您不会看到EXC_BAD_ACCESS
,因为向nil发送消息是正常的。
你的陈述“这些对象都是自动释放的额外保留”给我敲响了警钟,因为它表明你并不真正了解内存管理规则。应该在应用程序的整个生命周期中存在的全局对象不需要自动释放。你应该在开始时分配它,就是这样。或者,考虑使您的应用委托的全局对象属性委托。
如果你仍然无法弄明白,ade的答案的第一部分是要走的路(NSZombie),虽然第二部分可能无济于事。我怀疑你的物体是否会变成零。
答案 1 :(得分:0)
对我来说,这绝对听起来像是记忆问题。如果它是一个巨大的内存泄漏,你也不一定会收到内存警告。
您是否尝试使用XCode对游戏进行分析?如果是内存问题,您将在分析期间崩溃时看到它。
答案 2 :(得分:0)
“对象都是自动释放”听起来像是可能的原因。自动释放在UI“出现空气”时生效(控制从当前的UI事件处理逻辑返回),如果你有一个自动释放的变量,你希望在此之后继续使用它,它可能已经变成了“poof” (虽然经常可以使用它一段时间,直到重复使用存储)。
您是否运行过分析仪或任何其他工具?
答案 3 :(得分:0)
如果我理解正确,那么“自动释放额外保留”听起来像一个问题(除非你还有额外的版本),这可能会导致泄漏导致内存使用量增加导致其他对象被释放,然后你随后试着访问。
尝试启用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
}
答案 4 :(得分:0)
首先要意识到的是,可能对EXC_BAD_ACCESS
发生的代码没有任何问题。但是,为了以防万一,值得调查这个特定的对象。追求的第一个途径是其中一个对象已被解除分配,因为它没有得到足够的保留。所以,是的,绝对按照其他人的建议启用Zombies。
发布的对象可能无法立即解除分配。这就是为什么EXC_BAD_ACCESS
可能在奇怪的时候发生的原因。只有在不再需要它们使用的内存块时才会释放对象。其他对象当然会使用相同的块。因此,当该块有资格进行重新分配时,作为开发人员无法控制。这是由运行时处理的。
最好的办法是首先运行分析仪,然后运行仪器,使用泄漏仪器进行分析。
对于所有这些以及关于追踪Lou Franco网站上EXC_BAD_ACCESS
错误的逐点建议有一个很好的解释: