3

在一次采访中,我被要求实现 NSArray 的 exchangeObjectAtIndex:withObjectAtIndex: 方法。我写了以下代码:

- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
  id tmp = [self objectAtIndex:index1];
  [self replaceObjectAtIndex:index1 withObject:[self objectAtIndex:index2]];
  [self replaceObjectAtIndex:index2 withObject:tmp];
}

面试官说这是第一行的内存管理问题,我要赶上 bad_access_exc。他建议这样写:

- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
    id tmp = [[[self objectAtIndex:index1] retain] autorelease];
    [self replaceObjectAtIndex:index1  withObject:[self objectAtIndex:index2]];
    [self replaceObjectAtIndex:index2 withObject:tmp];
}

我知道他的代码是正确的,但是由于 tmp 是局部变量并且将被分配,所以没有释放,一切都会好起来的。有什么错误吗?

4

2 回答 2

3

如果您使用手动内存管理,则会出现错误。Apple在Advanced Memory Management Programming Guide中的“避免导致正在使用的对象的重新分配”下记录了该问题。

具体来说,objectAtIndex:retainautorelease它返回给您的对象。因此,NSArray可能只有对该对象的“拥有”引用。分配给tmp手动保留计数 (MRC) 不会保留对象,因此tmp不拥有它,自动释放池也不拥有它。

这意味着当您的方法的第 2 行发送[self replaceObjectAtIndex:index1 withObject:[self objectAtIndex:index2]]时,该数组可能会释放对该对象的最后一个引用,从而释放它。此时,tmp指的是一个已释放的对象;这称为“悬空引用”。

然后在第 3 行,您尝试将悬空引用放入数组中。数组将发送retain到无效的引用,您将崩溃或遇到堆损坏。

在 ARC 下,分配给tmp 确实保留了对象,因此在这种情况下没有错误。

于 2013-07-20T00:54:34.213 回答
2

请记住,这id tmp只不过是指向数组中对象的指针。它没有说明它指向的对象的内存管理。

...它将被分配,所以没有释放...

这是这里的症结所在。您不能保证将 at 的对象index1替换为 at 的对象时不会释放它index2。事实上,数组会release在此时调用它,以平衡retain它在对象最初添加到数组时调用的对象。因此,当对象 atindex1被替换时,at 对象index2的引用计数index1可能会变为零,对象将被释放,并且您的tmp变量将变成一个悬空指针。舞蹈使对象保持足够长的... retain] autorelease]时间来进行交换,而不必担心它在方法结束之前释放(很可能它会一直停留到下一个运行循环的顶部)。

于 2013-07-20T00:53:57.137 回答