为什么我的Objective-C对象被解除分配?

时间:2010-09-24 21:56:04

标签: iphone objective-c memory

我遇到了一个神秘释放的Objective-C对象(在iOS游戏应用中)的问题。

该对象是一个GameCharacter实例,它实例化如下:

for (int c = 0; c < kNrOfGuards; c++) {
    GameCharacter* guard = [[GameCharacter alloc] initGuard:self sprite:guardSprite];
    [characterArray addObject:guard];
    [guard release];
}

我还有一个方便的方法来找到GameCharacter:

- (GameCharacter*)findCharacterWithIndex:(int)index {
    return [characterArray objectAtIndex:index];
}

生成错误的代码如下所示:

for (int c = 0; c < [self characterCount]; c++) {
    GameCharacter* tempCharacter = [self findCharacterWithIndex:c];
    if (tempCharacter.playerId == playerIndex]) {
        ...
    }
}

运行此代码一段时间(从不立即)在控制台中生成错误:

[GameCharacter playerId]:发送到解除分配的实例0x4e47560的消息

使用NSZombieEnabled trick我已设法找到导致问题的对象,但我仍然无法理解为什么此对象正在被释放。搜索我的代码“release”/“dealloc”不会产生任何线索。

我已经尝试删除“release”(甚至添加“retain”!)到alloc / init循环(参见顶部),它似乎延长了应用程序可以运行的时间但不能完全删除问题。

任何提示都会非常感激!

修改

感谢quixoto,Olie,Eiko,tc。,我发现它是我的GameCharacter对象正被释放,但我仍然不明白为什么。以下是反向时间顺序的跟踪日志:

#0 -[GameCharacter dealloc]
#1 objc_setProperty
#2 -[TiledGroundLayer setSelectedCharacter:]
#3 -[TiledGroundLayer selectNextCharacterForPlayer:searchStep:]
#4 -[GameScene selectNextCharacter:]
#5 -[GameScene endTurn]
#6 -[HUDLayer onClickDone:]

这里发生的是,用户点击“完成”,屏幕上的所选字符被更改,因此TiledGroundLayer上的属性selectedCharacter被更改(步骤#2-4)。由于selectedCharacter拥有之前的GameCharacter对象,因此它似乎正在被释放。但是为什么NSMutableArray([characterArray addObject:guard];)没有正确保留它?

4 个答案:

答案 0 :(得分:2)

这里没有足够的代码来说明问题是什么,但是从错误消息中我猜测playerId对象是不被保留的。也就是说,似乎你的tempCharacter很好,但不是playerId字段。

如果你有

@property(nonatomic,retain) SomeObject *playerId;

然后记住那个

playerId = foo;

NOT 为您保留对象。您必须使用访问者:

self.playerId = foo;

编辑以回应Tom的问题编辑:

我绝对肯定地保证放置在NSMutableArray中的对象被该数组保留,直到(a)它们被移除或(b)数组被释放。所以你可以停止在那里寻找,问题出在其他地方。 :)

您可以尝试的一件事是将以下代码添加到您认为不应该发布的对象时:

#pragma mark -
#pragma mark Memory-use debugging

#define DEBUG_RETAIN_RELEASE    0
#define DEBUG_ALLOC_DEALLOC     0



#if DEBUG_ALLOC_DEALLOC

static int allocCounter = 0;
+(id)alloc
{
    id me = [super alloc];
    NSLog(@"%@ ALLOC (%2d):   %@", [me class], ++allocCounter, me);

    return me;
}

#endif


#if DEBUG_RETAIN_RELEASE
- (id)retain
{
    id result = [super retain];
    NSLog(@"%@ retain      %@, count: %2d", [self class], self, [self retainCount]);
    return result;
}


- (void)release
{
    // we have to log BEFORE the release, in case it's the last one! e
    NSLog(@"%@ RELEASE     %@, count: %2d", [self class], self, ([self retainCount] - 1));
    [super release];
}


- (id)autorelease
{
    id result = [super autorelease];
    NSLog(@"%@ AUTOrelease %@, count: %2d", [self class], self, [self retainCount]);
    return result;
}

#endif

// ...


- (void)dealloc
{
#if DEBUG_ALLOC_DEALLOC
    NSLog(@"%@ dealloc (%2d): %@", [self class], --allocCounter, self);
#endif

    [self releaseMyStuff];
    [super dealloc];
}

然后从DEBUG_ALLOC_DEALLOC = 1开始,并在dealloc日志语句中放置一个断点。如果这没有用,请设置DEBUG_RETAIN_RELEASE = 1并中断retain&amp;释放。

你会对你获得的所有iOS保留感到惊讶,但不要担心,如果使用得当,iOS承诺平衡保留释放。我只是警告你,因为你可能期望保留计数低得多,并且看到它在某些操作或其他操作中升高时会感到惊讶。

运气!

答案 1 :(得分:1)

通过3个简单步骤调试虚假保留/释放:

  1. 覆盖您感兴趣的班级的-retain-release-autorelease。让他们记录一条消息(NSLog(@"%@ %s", self, sel_getName(_cmd)))和super - 致电
  2. 断点所有这些方法(在super - 调用,即在日志消息之后,以便知道它是哪个对象)。编辑断点;添加命令“bt”并检查自动继续框(或只使用两个命令“bt”,“继续”)。
  3. 清除日志。运行应用程序。打印出日志。把它贴在白板上。绘制一些箭头,直到找到虚假的release / autorelease
  4. 我的第一印象是characterArray过早发布,但这会导致它抱怨向已释放的NSArray发送消息。当然,除非您从多个线程访问characterArray(不要这样做!)。

答案 2 :(得分:1)

根据您的更新:

#0 -[GameCharacter dealloc]
#1 objc_setProperty
#2 -[TiledGroundLayer setSelectedCharacter:]

我猜你会释放你对setter中对象的现有引用,然后保留新副本。但是,如果新对象恰好是与现有引用完全相同的对象,则可能会将retain消息发送到已经解除分配的对象。

-(void) setSelectedCharacter: (GameCharacter*) newCharacter
{
  [character release]; // Oops if character == newCharacter
  character = [newCharacter retain];
} 

答案 3 :(得分:0)

您在某处释放GameCharacter实例。你的代码看起来不错,所以它在其他地方的某个地方使用这些对象。