我刚刚开始研究 Objective-C 和 Cocoa,以便使用 iPhone SDK。我对 Cmalloc
和free
概念相当满意,但 Cocoa 的引用计数方案让我很困惑。有人告诉我,一旦你理解它,它就非常优雅,但我还没有完全理解它。
和如何release
工作以及它们的使用约定是什么?retain
autorelease
(或者失败了,你读到了什么帮助你得到它?)
我刚刚开始研究 Objective-C 和 Cocoa,以便使用 iPhone SDK。我对 Cmalloc
和free
概念相当满意,但 Cocoa 的引用计数方案让我很困惑。有人告诉我,一旦你理解它,它就非常优雅,但我还没有完全理解它。
和如何release
工作以及它们的使用约定是什么?retain
autorelease
(或者失败了,你读到了什么帮助你得到它?)
让我们从retain
和开始release
;autorelease
一旦您了解了基本概念,这实际上只是一个特例。
在 Cocoa 中,每个对象都会跟踪它被引用的次数(具体来说,NSObject
基类实现了这一点)。通过调用retain
一个对象,你是在告诉它你想将它的引用计数加一。通过调用release
,您告诉对象您正在释放它,并且它的引用计数递减。如果在调用 之后release
,引用计数现在为零,则系统会释放该对象的内存。
malloc
与此不同的基本方式free
是任何给定对象都不需要担心系统的其他部分崩溃,因为您已经释放了它们正在使用的内存。假设每个人都在按照规则进行保留/释放,当一段代码保留然后释放对象时,任何其他引用该对象的代码都不会受到影响。
有时可能令人困惑的是知道在什么情况下应该调用retain
and release
。我的一般经验法则是,如果我想在某个对象上挂起一段时间(例如,如果它是类中的成员变量),那么我需要确保对象的引用计数知道我。如上所述,对象的引用计数通过调用retain
. 按照惯例,当使用“init”方法创建对象时,它也会增加(实际上设置为 1)。在这两种情况下,我有责任在release
完成后调用该对象。如果我不这样做,就会有内存泄漏。
对象创建示例:
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
被调用时,当前线程NSAutoreleasePool
会收到调用警报。现在NSAutoreleasePool
知道一旦它获得机会(在事件循环的当前迭代之后),它就可以调用release
该对象。从我们作为程序员的角度来看,它负责调用release
我们,所以我们不必(事实上,我们不应该)。
需要注意的重要一点是(再次按照惯例)所有对象创建类方法都返回一个自动释放的对象。例如,在下面的示例中,变量“s”的引用计数为 1,但在事件循环完成后,它将被销毁。
NSString* s = [NSString stringWithString:@"Hello World"];
如果您想挂在该字符串上,则需要retain
显式调用,然后release
在完成后显式调用。
考虑以下(非常人为的)代码,您会看到需要的情况autorelease
:
- (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];
}
我意识到这一切有点令人困惑——不过,在某些时候,它会点击。这里有一些参考资料可以帮助您:
如果您了解保留/释放的过程,那么有两条黄金法则对于成熟的 Cocoa 程序员来说是显而易见的,但不幸的是,对于新手来说很少清楚地阐明这一点。
如果返回对象的函数具有alloc
,create
或copy
在其名称中,则该对象是您的。完成后必须打电话[object release]
。或者CFRelease(object)
,如果它是 Core-Foundation 对象。
如果它的名称中没有这些词之一,则该对象属于其他人。[object retain]
如果您希望在函数结束后保留对象,则必须调用。
在您自己创建的函数中也遵循此约定会很好。
(吹毛求疵者:是的,不幸的是,有一些 API 调用是这些规则的例外,但它们很少见)。
如果您正在为桌面编写代码并且可以针对 Mac OS X 10.5,那么您至少应该考虑使用 Objective-C 垃圾收集。它确实会简化您的大部分开发工作——这就是为什么 Apple 首先投入所有精力来创建它,并使其表现良好。
至于不使用GC时的内存管理规则:
+alloc/+allocWithZone:
,或创建一个新对象+new
,或者如果您是一个对象,您将获得它的所有权并且必须确保它被发送。-copy
-mutableCopy
-retain
-release
-release
。-release
您可以自己发送,也可以发送对象-autorelease
,当前自动释放池将在池耗尽时发送它-release
(每次接收一次)。-autorelease
通常-autorelease
用作确保对象在当前事件的长度内存活的一种方式,但之后会被清理,因为围绕 Cocoa 的事件处理有一个自动释放池。在 Cocoa 中,将自动释放的对象返回给调用者比返回调用者本身需要释放的对象要常见得多。
Objective-C 使用引用计数,这意味着每个对象都有一个引用计数。创建对象时,它的引用计数为“1”。简单地说,当一个对象被引用(即存储在某个地方)时,它会被“保留”,这意味着它的引用计数会增加一。当不再需要一个对象时,它被“释放”,这意味着它的引用计数减一。
当对象的引用计数为 0 时,该对象被释放。这是基本的引用计数。
对于某些语言,引用会自动增加和减少,但objective-c 不是这些语言之一。因此程序员负责保留和释放。
编写方法的典型方式是:
id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;
需要记住在代码中释放任何获取的资源的问题既乏味又容易出错。Objective-C 引入了另一个旨在使这变得更容易的概念:自动释放池。自动释放池是安装在每个线程上的特殊对象。如果您查看 NSAutoreleasePool,它们是一个相当简单的类。
当一个对象收到发送给它的“自动释放”消息时,该对象将查找位于当前线程堆栈上的任何自动释放池。它会将对象作为对象添加到列表中,以在将来的某个时间向其发送“释放”消息,这通常是在池本身被释放时。
使用上面的代码,您可以通过以下方式将其重写为更短且更易于阅读:
id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;
因为对象是自动释放的,我们不再需要显式调用它的“释放”。这是因为我们知道一些自动释放池稍后会为我们做这件事。
希望这会有所帮助。维基百科的文章关于引用计数非常好。更多关于自动释放池的信息可以在这里找到。另请注意,如果您正在为 Mac OS X 10.5 及更高版本构建,您可以告诉 Xcode 在启用垃圾收集的情况下构建,从而允许您完全忽略保留/释放/自动释放。
Joshua (#6591) - Mac OS X 10.5 中的垃圾收集功能看起来很酷,但不适用于 iPhone(或者如果您希望您的应用程序在 Mac OS X 10.5 之前的版本上运行)。
此外,如果您正在编写一个库或可能被重用的东西,使用 GC 模式会锁定任何使用代码的人也使用 GC 模式,所以据我了解,任何试图编写广泛可重用代码的人都倾向于去管理手动记忆。
与以往一样,当人们开始尝试重新编写参考资料时,他们几乎总是会出错或提供不完整的描述。
Apple 在Memory Management Programming Guide for Cocoa中对 Cocoa 的内存管理系统进行了完整的描述,最后对内存管理规则进行了简短而准确的总结。
除了您可能想考虑花 50 美元购买 Hillegass 书之外,我不会添加保留/释放的具体内容,但我强烈建议您在应用程序开发的早期就开始使用 Instruments 工具(甚至是您的第一!)。为此,运行-> 使用性能工具开始。我将从 Leaks 开始,它只是众多可用工具中的一种,但它有助于在您忘记释放时向您展示。您将看到多少信息不再令人生畏。但是请查看本教程以快速入门:
COCOA 教程:使用仪器修复内存泄漏
实际上,尝试强制泄漏可能是学习如何防止泄漏的更好方法!祝你好运 ;)
返回 [[s autorelease] 释放];
自动释放不保留对象。Autorelease 只是将它放入队列中,以便稍后发布。你不想在那里有发布声明。
我平时收集的 Cocoa 内存管理文章:
NilObject 的回答是一个好的开始。这里有一些关于手动内存管理的补充信息(iPhone 需要)。
如果您个人alloc/init
使用一个对象,它的引用计数为 1。当不再需要它时,您负责清理它,方法是调用[foo release]
或[foo autorelease]
. release 会立即清理它,而 autorelease 会将对象添加到自动释放池中,稍后会自动释放它。
autorelease 主要用于当您有一个需要返回相关对象的方法时(因此您不能手动释放它,否则您将返回一个 nil 对象)但您也不想持有它.
如果你获取了一个没有调用 alloc/init 来获取它的对象——例如:
foo = [NSString stringWithString:@"hello"];
但是你想挂在这个对象上,你需要调用[foo retain]。否则,它可能会得到autoreleased
并且您将持有一个 nil 引用(就像在上面的stringWithString
示例中那样)。当您不再需要它时,调用[foo release]
.
iDeveloperTV Network 提供免费的截屏视频
上面的答案清楚地重述了文档所说的内容;大多数新人遇到的问题是无证案件。例如:
Autorelease:文档说它将在“未来的某个时候”触发发布。什么时候?!基本上,在您将代码退出回系统事件循环之前,您可以依靠该对象。系统可以在当前事件循环之后的任何时间释放对象。(我认为马特早些时候说过。)
静态字符串:NSString *foo = @"bar";
——你必须保留还是释放它?不,怎么样
-(void)getBar {
return @"bar";
}
...
NSString *foo = [self getBar]; // still no need to retain or release
创建规则:如果你创建了它,你就拥有它,并期望发布它。
一般来说,新 Cocoa 程序员搞砸的方式是不了解哪些例程返回带有retainCount > 0
.
保留计数规则
- 在给定块内,-copy、-alloc 和 -retain 的使用应该与 -release 和 -autorelease 的使用相同。
- 使用便利构造函数(例如 NSString 的 stringWithString)创建的对象被认为是自动释放的。
- 实现一个 -dealloc 方法来释放你拥有的实例变量
第一个项目符号说:如果您调用alloc
(或new fooCopy
),则需要对该对象调用 release。
第二个要点说:如果您使用便利构造函数并且需要对象挂起(如稍后要绘制的图像),则需要保留(然后释放)它。
第三个应该是不言自明的。
正如一些人已经提到的,Apple 的内存管理简介是迄今为止最好的起点。
我还没有提到的一个有用的链接是Practical Memory Management。如果您通读它们,您会在 Apple 文档的中间找到它,但值得直接链接。这是内存管理规则的精彩执行摘要,其中包含示例和常见错误(基本上这里的其他答案试图解释,但不是很好)。