5

我一直在查看 StackOverflow 上提出的问题,但是关于 Objective-C 中的内存管理的问题太多了,我找不到我想要的答案。

问题是在将新创建的对象添加到集合(如 NSMutableArray)之前调用 autorelease 是否可以(并且推荐)?或者我应该在添加后明确释放它。(我知道 NSMutableArray 将保留该对象)

这说明了我的问题:

场景 A(自动释放):

- (void) add {
   // array is an instance of NSMutableArray

   MyClass *obj = [[[MyClass alloc] init] autorelease];

   [array addObject:obj];
}

场景 B(显式发布):

- (void) add {
   // array is an instance of NSMutableArray

   MyClass *obj = [[MyClass alloc] init];

   [array addObject:obj];

   [obj release];
}

我认为两者都是正确的,但我不确定,而且我肯定不知道首选方式是什么。

Objective-C 大师能否对此有所了解?

4

7 回答 7

12

恕我直言,哪种方式“正确”是一个偏好问题。我不反对主张不使用的响应者autorelease,但我更喜欢使用,autorelease除非有绝对令人信服的理由不使用。我将列出我的原因,您可以决定它们是否适合您的编程风格。

正如 Chuck 所指出的,有一个半都市传说,即使用自动释放池会产生某种开销。这与事实相去甚远,这来自于使用 Shark.app 花费了无数小时从代码中榨取最后一点性能。尝试对此进行优化是“过早优化”领域的深度。当且仅当 Shark.app 为您提供硬数据时,您甚至应该考虑查看它,这可能是一个问题。

正如其他人指出的那样,自动释放的对象是“稍后释放”的。这意味着它们会徘徊,占用内存,直到“稍后的点”滚动。对于“大多数”情况,这是在运行循环休眠直到下一个事件(计时器、用户单击某物等)之前的事件处理过程的底部。

但是,有时,您需要尽快摆脱这些临时对象,而不是稍后。例如,您需要处理一个巨大的、数兆字节的文件,或者数据库中的数万行。发生这种情况时,您需要将 a 放置NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];在精心选择的点上,然后[pool release];在底部放置 a。这几乎总是发生在某种“循环批处理”中,因此它通常位于某些关键循环的开始和底部。同样,这应该基于证据,而不是基于预感。Instrument.app 的 ObjectAlloc 是您用来查找这些问题点的工具。

不过,我更喜欢 的主要原因是autorelease编写无泄漏程序要容易得多。简而言之,如果您选择走的路线,您需要保证最终发送到,在任何情况下。虽然这看起来很简单,但实际上很难做到。以你的例子为例:releasereleasereleaseobj

   // array is an instance of NSMutableArray
   MyClass *obj = [[MyClass alloc] init];
   [array addObject:obj];
   // Assume a few more lines of work....
   [obj release];

现在想象一下,由于某种原因,某处某事巧妙地违反了您array的可变假设,可能是由于使用某种方法来处理结果,并且包含已处理结果的返回数组被创建为NSArray. 当您发送addObject:到该 immutableNSArray时,将引发异常,并且您将永远不会发送obj它的release消息。obj或者可能在 when was allocd 和所需调用之间的某个地方出现了问题release,例如您检查了某些条件并立即错误地因为它让您忘记了以后必须return()进行该调用。release

你刚刚泄露了一个对象。并且可能签了几天,试图找出你在哪里以及为什么泄露它。根据经验,您将花费大量时间查看上面的代码,确信它不可能是泄漏的源头,因为您非常清楚地发送obj了一个release. 然后,几天后,当您对问题的原因有所启发时,您将体验到只能被描述为宗教顿悟的事情。

考虑以下autorelease情况:

   // array is an instance of NSMutableArray
   MyClass *obj = [[[MyClass alloc] init] autorelease];
   [array addObject:obj];
   // Assume a few more lines of work....

现在,发生什么不再重要,因为几乎不可能obj意外泄漏,即使在极其不寻常或特殊的极端情况下也是如此。

于 2009-07-19T01:22:05.743 回答
7

两者都是正确的,并且会按照您的预期工作。

我个人更喜欢使用后一种方法,但这只是因为我喜欢明确说明对象何时被释放。通过自动释放对象,我们所做的只是说“这个对象将在未来的某个任意时间点被释放”。这意味着您可以将自动释放的对象放入数组中,销毁数组,并且该对象可能(可能)仍然存在。

使用后一种方法,对象将立即与数组一起被销毁(前提是没有其他任何东西出现并在此期间保留它)。如果我处于内存受限的环境(例如 iPhone)中,我需要注意我正在使用多少内存,我将使用后一种方法,这样我就不会有太多的对象徘徊在其中NSAutoreleasePool 某处。如果内存使用对您来说不是一个大问题(而且通常对我来说也不是),那么这两种方法都是完全可以接受的。

于 2009-07-18T15:30:07.087 回答
3

它们都是正确的,但 B 可能是首选,因为它根本没有开销。自动释放使自动释放池负责对象。这有一个非常小的开销,当然,它会乘以所涉及的对象的数量。

因此,对于一个对象,A 和 B 或多或少是相同的,但绝对不要在有很多对象要添加到数组的场景中使用 A。

在不同的情况下,自动释放可能会延迟并累积在线程结束时释放许多对象。这可能是次优的。请注意,无论如何自动释放都会在没有明确干预的情况下发生很多。例如,许多 getter 是这样实现的:

return [[myObject retain] autorelease];

因此,每当您调用 getter 时,您都会将一个对象添加到自动释放池中。

于 2009-07-18T15:29:17.837 回答
0

您可以autorelease在任何时候发送消息,因为在应用程序的消息循环重复之前(即,直到您的所有方法都响应用户输入完成执行),它才会被执行。

http://macdevcenter.com/pub/a/mac/2001/07/27/cocoa.html?page=last&x-showcontent=text

于 2009-07-18T15:30:07.167 回答
0

你已经alloc'ed 对象,然后在某个时候释放它是你的工作。两个代码片段的工作方式相同,正确的autorelease方式是潜在的较慢的对应方式。

就个人而言,我更喜欢这种autorelease方式,因为它更容易打字,而且几乎从来都不是瓶颈。

于 2009-07-18T15:30:52.080 回答
0

他们都没事。有些人会因为“开销”或诸如此类的事情而告诉您避免自动释放,但事实是,实际上没有开销。继续进行基准测试并尝试找到“开销”。避免它的唯一原因是在 iPhone 等内存不足的情况下。在 OS X 上,您几乎拥有无限的内存,因此不会有太大的不同。只需使用对您最方便的那个。

于 2009-07-18T15:39:35.367 回答
0

为了简洁和“安全”,我更喜欢 A(自动释放),正如 johne 所说的那样。它简化了我的代码,而且我从来没有遇到过问题。

也就是说,直到今天:在将块添加到数组之前,我遇到了自动释放块的问题。请参阅我的 stackoverflow 问题: [myArray addObject:[[objcBlock copy] autorelease]] 在对数组进行解除分配时崩溃(更新:原来问题出在我的代码中的其他地方,但是自动释放的行为仍然存在细微差别...... )

于 2011-05-02T08:33:06.723 回答