我刚刚开始了解一下Objective-C和Cocoa,以了解如何使用iPhone SDK。我对C的malloc和free的概念相当满意,但cocoa的引用计数方案让我相当困惑。有人告诉我,一旦你理解了它,它就非常优雅,但我还没有过完这个难关。
release、retain和autorelease是如何工作的?它们的使用有哪些约定?
(否则,你读了什么帮助你得到它?)
让我们从retain和release开始,一旦你了解了基本概念,autorelease实际上只是一个特例。
在cocoa中,每个对象跟踪被引用的次数(具体来说,NSObject基类实现了这一点)。通过对一个对象调用retain,你告诉它你想要一个一个地增加它的引用计数。通过调用release,您可以告诉对象您要放弃它,并且它的引用计数会减少。如果在调用release之后,引用计数现在为零,则系统将释放该对象的内存。
这与malloc和free的基本区别在于,任何给定的对象都不需要担心系统的其他部分崩溃,因为您释放了它们正在使用的内存。假设每个人都按照规则进行游戏和保留/释放,当一段代码保留然后释放对象时,任何其他引用该对象的代码都不会受到影响。
有时会令人困惑的是,知道在什么情况下你应该称之为retain和release。我的一般经验法则是,如果我想在一个对象上停留一段时间(例如,如果它是一个类中的成员变量),那么我需要确保该对象的引用计数了解我。如上所述,通过调用retain来增加对象的引用计数。按照惯例,当使用"init"方法创建对象时,它也会递增(实际上设置为1)。在这两种情况下,我都有责任在我处理完之后就对象打电话给release。如果我不这样做,就会出现内存泄漏。
对象创建示例:
1 2 3 4 5
| NSString* s = [[NSString alloc] init]; // Ref count is 1
[s retain]; // Ref count is 2 - silly
// to do this after init
[s release]; // Ref count is back to 1
[s release]; // Ref count is 0, object is freed |
现在是autorelease。autorelease被用作一种方便(有时是必要的)的方法,告诉系统在一段时间后释放这个对象。从管道的角度来看,当调用autorelease时,当前线程的NSAutoreleasePool会收到调用的警报。NSAutoreleasePool现在知道,一旦它得到一个机会(在事件循环的当前迭代之后),就可以在对象上调用release。从我们作为程序员的角度来看,它负责为我们调用release,所以我们不必(实际上,我们不应该)。
需要注意的是(按照约定),所有对象创建类方法都返回一个自动释放的对象。例如,在下面的示例中,变量"s"的引用计数为1,但在事件循环完成后,它将被销毁。
1
| NSString* s = [NSString stringWithString:@"Hello World"]; |
如果您想挂起这个字符串,您需要显式调用retain,然后在完成后显式调用release。
考虑以下(非常人为的)代码,您将看到需要autorelease的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| - (NSString*)createHelloWorldString
{
NSString* s = [[NSString alloc] initWithString:@"Hello World"];
// Now what? We want to return s, but we've upped its reference count.
// The caller shouldn't be responsible for releasing it, since we're the
// ones that created it. If we call release, however, the reference
// count will hit zero and bad memory will be returned to the caller.
// The answer is to call autorelease before returning the string. By
// explicitly calling autorelease, we pass the responsibility for
// releasing the string on to the thread's NSAutoreleasePool, which will
// happen at some later time. The consequence is that the returned string
// will still be valid for the caller of this function.
return [s autorelease];
} |
我意识到所有这些都有点令人困惑——不过,在某个时候,它会点击。以下是一些参考资料:
- 苹果的内存管理简介。
- MacOSX的可可编程(第四版),作者AaronHillegas——一本写得很好的书,有很多很好的例子。它读起来像一个教程。
- 如果你真的要潜入,你可以去大呆子牧场。这是一个训练设施运行的亚伦希勒加-作者的书上面提到。几年前我在那里参加了可可介绍课程,这是一个很好的学习方法。
如果您了解保留/发布的过程,那么有两个黄金规则对于已建立的Cocoa程序员来说是显而易见的,但不幸的是,对于新来者来说,很少清楚地阐明这一点。
如果返回一个对象的函数的名称中有alloc、create或copy,那么该对象就是您的。你完成后必须打电话给[object release]。或者EDOCX1×16,如果它是一个核心基础对象。
如果它的名称中没有这些单词中的一个,那么对象就属于其他人。如果希望在函数结束后保留对象,则必须调用[object retain]。
在您自己创建的函数中,您也应该遵循这个约定。
(吹毛求疵者:是的,不幸的是,有一些API调用是这些规则的例外,但它们很少)。
如果您正在为桌面编写代码,并且可以以Mac OS X 10.5为目标,那么至少应该考虑使用Objective-C垃圾收集。它真的会简化你的大部分开发——这就是为什么苹果公司把所有的努力都放在了创建它的第一位,并使它运行良好。
对于不使用GC时的内存管理规则:
- 如果使用+alloc/+allocWithZone:、+new、-copy或-mutableCopy创建新对象,或者使用-retain对象创建新对象,则表示您拥有该对象,并且必须确保该对象已发送-release。
- 如果您以任何其他方式接收到一个对象,则您不是该对象的所有者,不应确保该对象已发送到cx1〔5〕。
- 如果您想确保发送对象-release,您可以自己发送,也可以发送对象-autorelease,当前自动释放池将在池排空时发送对象-release(每次接收一次-autorelease)。
通常情况下,-autorelease用作确保对象在当前事件的长度内存活,但随后被清除的一种方法,因为cocoa的事件处理周围有一个自动释放池。在Cocoa中,将对象返回给自动释放的调用者要比返回调用者自己需要释放的对象要常见得多。
除了您可能会考虑降低50美元并获得Hillegass图书之外,我不会在保留/发布的具体内容中添加其他内容,但我强烈建议您在开发应用程序(甚至是您的第一个应用程序)的早期就开始使用仪器工具!。为此,请运行->从性能工具开始。我将从泄漏开始,这只是许多可用仪器中的一个,但当您忘记释放时,它将有助于向您展示。你将得到多少信息,这让人望而却步。但是,请查看本教程以快速起床:Cocoa教程:使用仪器修复内存泄漏
实际上,尝试强制泄漏可能是更好的方法,反过来,学习如何防止泄漏!祝你好运!)
和以往一样,当人们开始尝试重新表述参考资料时,他们几乎总是会出错或提供不完整的描述。
苹果公司为Cocoa提供了一份完整的《Cocoa内存管理系统内存管理编程指南》,最后对内存管理规则进行了简要而准确的总结。
Joshua(6591)-Mac OS X 10.5中的垃圾收集功能看起来很酷,但不适用于iPhone(或者如果您希望应用程序在Mac OS X 10.5之前的版本上运行)。
另外,如果您正在编写一个库或一些可以重用的东西,那么使用gc模式会将使用代码的任何人锁定为也使用gc模式,因此我理解,任何试图编写广泛可重用代码的人都倾向于手动管理内存。
Objective-C使用引用计数,这意味着每个对象都有一个引用计数。创建对象时,其引用计数为"1"。简单地说,当一个对象被引用(即存储在某个地方)时,它会被"保留",这意味着它的引用计数会增加一个。当一个对象不再需要时,它会被"释放",这意味着它的引用计数会减少一个。
当对象的引用计数为0时,对象将被释放。这是基本参考计数。
对于某些语言,引用会自动增加和减少,但Objective-C不是这些语言中的一种。因此,程序员负责保留和释放。
编写方法的典型方法是:
1 2 3 4
| id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue; |
需要记住在代码中释放任何获得的资源的问题既单调又容易出错。Objective-C引入了另一个概念,旨在使这更容易:自动释放池。自动释放池是安装在每个线程上的特殊对象。如果你查一下NSAutoReleasePool,它们是一个相当简单的类。
当对象收到发送给它的"autorelease"消息时,该对象将查找当前线程堆栈上的任何autorelease池。它将把对象作为一个对象添加到列表中,以便在将来的某个时候向其发送一条"释放"消息,这通常是在池本身被释放的时候。
使用上面的代码,您可以通过说:
1 2 3
| id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue; |
因为对象是自动释放的,所以我们不再需要在它上面显式地调用"release"。这是因为我们知道一些自动释放池稍后会为我们做这件事。
希望这有帮助。维基百科的这篇文章非常适合参考计数。有关自动释放池的详细信息,请参阅此处。另外请注意,如果您正在为Mac OS X 10.5和更高版本构建,您可以告诉Xcode在启用垃圾收集的情况下进行构建,这样您就可以完全忽略retain/release/autorelease。
我通常收集的可可记忆管理文章:
可可存储器管理
Matt Dillard wrote:
return [▼显示 release];
自动释放不保留对象。autorelease只是将其放入队列中,以便稍后释放。您不希望在那里有一个发布声明。
IDeveloperTV网络提供免费的屏幕广播
目标C中的记忆管理
NilObject的回答是一个很好的开始。以下是一些关于手动内存管理的补充信息(在iPhone上是必需的)。
如果您个人使用一个对象,它会附带一个引用计数1。当不再需要时,您有责任在之后进行清理,可以致电[foo release]或[foo autorelease]。release会立即清除它,而autorelease会将对象添加到autorelease池,稍后会自动释放它。
autorelease主要用于当您有一个方法需要返回有问题的对象时(这样您就不能手动释放它,否则您将返回一个nil对象),但您也不想保留它。
如果获取的对象没有调用alloc/init来获取它——例如:
1
| foo = [NSString stringWithString:@"hello"]; |
但如果你想抓住这个对象,你需要调用[foo retain]。否则,它可能会得到autoreleased,而您将保持零引用(就像上面的stringWithString示例中那样)。当你不再需要它时,打电话给[foo release]。
上面的答案清楚地重述了文档中的内容;大多数新人遇到的问题是未记录的案例。例如:
自动释放:文件说它将触发一个释放"在未来的某个时刻"。什么时候?!基本上,在将代码退出到系统事件循环之前,可以依靠对象的存在。在当前事件周期之后,系统可以随时释放对象。(我想马特之前说过。)
静态字符串:NSString *foo = @"bar";--您需要保留还是释放它?不,怎么样?
1 2 3
| -(void)getBar {
return @"bar";
} |
…
1
| NSString *foo = [self getBar]; // still no need to retain or release |
创建规则:如果您创建了它,您就拥有它,并希望释放它。
一般来说,新的Cocoa程序员搞砸的方式是不了解哪些例程返回带有retainCount > 0的对象。
以下是Cocoa内存管理非常简单的规则中的一个片段:
Retention Count rules
-
Within a given block, the use of -copy, -alloc and -retain should equal the use of -release and -autorelease.
-
Objects created using convenience constructors (e.g. NSString's stringWithString) are considered autoreleased.
-
Implement a -dealloc method to release the instancevariables you own
第一颗子弹说:如果你打电话给alloc或new fooCopy,你需要打电话给那个物体。
第二个要点是:如果您使用一个方便的构造函数,并且您需要该对象挂在周围(就像后面要绘制的图像一样),那么您需要保留(然后再释放)它。
第三个应该是不言而喻的。
还有很多关于可可豆的好信息:
正如一些人已经提到的,苹果的内存管理介绍是迄今为止最好的开始。
我还没有看到一个有用的链接,那就是实用的内存管理。如果你仔细阅读,你会发现它在苹果文档的中间,但它值得直接链接。这是一个关于记忆管理规则的精彩的执行摘要,有例子和常见的错误(基本上这里的其他答案是试图解释的,但不是同样的)。