理解使用 Cocoa 和 Objective-C 进行引用计数

我刚刚开始了解 Objective-C 和 Cocoa,以便使用 iPhone SDK。我对 C 的 mallocfree概念相当满意,但是 Cocoa 的引用计数方案让我相当困惑。有人告诉我,一旦你理解了它,它就会变得非常优雅,但我只是还没有走出低谷。

releaseretainautorelease是如何工作的? 它们的使用惯例是什么?

(如果没有,你读了哪些帮助你得到它的书?)

35878 次浏览

Objective-C 使用 Reference Counting,这意味着每个 Object 都有一个引用计数。当创建一个对象时,它的引用计数为“1”。简单地说,当一个对象被引用(例如,存储在某个地方)时,它得到“保留”,这意味着它的引用计数增加了一。当一个对象不再需要时,它被“释放”,这意味着它的引用计数减少了一。

When an object's reference count is 0, the object is freed. This is basic reference counting.

对于某些语言,引用会自动增加或减少,但 Objective-c 不是其中之一。因此,程序员负责保留和释放。

编写方法的一个典型方法是:

id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;

需要记住在代码中释放任何获得的资源的问题既冗长又容易出错。Objective-C 引入了另一个旨在简化此工作的概念: 自动发布池。自动发布池是安装在每个线程上的特殊对象。如果您查找 NSAutorelease asePool,它们是一个相当简单的类。

当对象收到发送给它的“ autorelease”消息时,该对象将查找堆栈上当前线程的任何自动发布池。它将把对象作为一个对象添加到列表中,以便在将来的某个时候向其发送“发布”消息,这通常是在池本身被释放的时候。

拿上面的代码来说,你可以重写一下,让它更简短,更容易阅读,你可以这样说:

id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;

Because the object is autoreleased, we no longer need to explicitly call "release" on it. This is because we know some autorelease pool will do it for us later.

希望这能帮上忙。维基百科的文章在引用计数方面做得很好。更多关于 自动释放池可以在这里找到的信息。还要注意,如果您正在为 Mac OS X 10.5及更高版本构建,可以告诉 Xcode 在启用垃圾收集的情况下进行构建,从而允许您完全忽略保留/发布/自动发布。

Joshua (# 6591)-Mac OS X 10.5中的垃圾收集工具看起来很酷,但是不适用于 iPhone (或者如果你想让你的应用程序运行在 Mac OS X 10.5之前的版本上)。

Also, if you're writing a library or something that might be reused, using the GC mode locks anyone using the code into also using the GC mode, so as I understand it, anyone trying to write widely reusable code tends to go for managing memory manually.

NilObject 的答案是一个良好的开端。

如果您个人使用 alloc/init对象,它的引用计数为1。当它不再需要的时候,你要负责清理,无论是通过调用 [foo release]还是 [foo autorelease]。Release 会立即清理对象,而 autorelease 会将对象添加到 autorelease 池中,后者会在以后自动释放对象。

Autorelease 主要用于当您有一个方法需要返回有问题的对象(所以你不能手动释放它,否则你会返回一个 nil 对象) ,但是您也不想保留它时。

如果您获取了一个对象,但没有调用 alloc/init 来获取它——例如:

foo = [NSString stringWithString:@"hello"];

但是如果你想保留这个对象,你需要调用[ foo reture]。否则,它可能会得到 autoreleased,而您将持有一个空引用 (就像在上面的 stringWithString例子中一样)。当您不再需要它时,调用 [foo release]

让我们从 retainrelease开始; 一旦理解了基本概念,autorelease实际上只是一个特例。

在 Cocoa 中,每个对象都跟踪它被引用了多少次(具体来说,NSObject基类实现了这一点)。通过对一个对象调用 retain,你告诉它你想把它的引用计数提高一。通过调用 release,您可以告诉对象您正在放开它,并且它的引用计数是递减的。如果在调用 release之后,引用计数现在为零,那么系统将释放该对象的内存。

这与 mallocfree的基本区别在于,任何给定的对象都不需要担心系统的其他部分崩溃,因为您已经释放了它们正在使用的内存。假设每个人都在按照规则玩游戏并保留/释放对象,当一段代码保留然后释放对象时,任何其他引用对象的代码都不会受到影响。

有时令人困惑的是在什么情况下应该调用 retainrelease。我的一般经验法则是,如果我想持有一个对象一段时间(例如,如果它是一个类中的成员变量) ,那么我需要确保对象的引用计数知道我。如上所述,通过调用 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,所以我们不必这样做(事实上,我们也不应该这样做)。

值得注意的是(同样,按照惯例)所有对象创建 class方法都返回一个自动释放的对象。例如,在下面的示例中,变量“ s”的引用计数为1,但是在事件循环完成后,它将被销毁。

NSString* s = [NSString stringWithString:@"Hello World"];

如果要保留该字符串,则需要显式调用 retain,然后在完成后显式调用 release

Consider the following (very contrived) bit of code, and you'll see a situation where autorelease is required:

- (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];
}

我意识到所有这一切都有点令人困惑——不过,在某个时刻,它会发生作用。这里有一些参考资料可以帮助你:

  • Apple's introduction to memory management.
  • Cocoa Programming for Mac OS X (4th Edition), by Aaron Hillegas - a very well written book with lots of great examples. It reads like a tutorial.
  • 如果你真的投入其中,你可以去 大书呆农场。这是一个由 Aaron Hillegas 经营的培训机构——上面提到的书的作者。几年前,我在那里参加了可可的入门课程,这是一个很好的学习方式。

如果您正在为桌面编写代码,并且目标是 Mac OS X 10.5,那么您至少应该考虑使用 Objective-C 垃圾收集。它真的会简化你的大部分开发ーー这就是为什么苹果公司把所有的努力放在第一位来创建它,并使它表现良好。

至于不使用 GC 时的内存管理规则:

  • 如果您使用 +alloc/+allocWithZone:+new-copy-mutableCopy创建一个新对象,或者如果您使用 -retain创建一个对象,那么您将获得该对象的所有权,并且必须确保它被发送到 -release
  • 如果您以任何其他方式接收到一个对象,那么您就是该对象的所有者 没有,应该由 没有确保将其发送给 -release
  • 如果你想确保一个对象被发送 -release你可以自己发送,或者你可以发送对象 -autorelease和当前的 自动释放池将发送它 -release(每接收到的 -autorelease一次)当池被抽干。

通常使用 -autorelease来确保对象在当前事件的长度内存活,但随后会被清理,因为 Cocoa 的事件处理周围有一个自动释放池。在 Cocoa 中,将对象返回给自动释放的调用方比返回调用方本身需要释放的对象更为常见。

如果您理解了保留/发布的过程,那么有两条黄金法则对于已建立的 Cocoa 程序员来说是显而易见的,但是不幸的是,对于新手来说很少清楚地说明这一点。

  1. 如果一个返回对象的函数的名称中有 alloccreate或者 copy,那么这个对象就是你的。完成后必须调用 [object release]。或者 CFRelease(object),如果它是核心基础对象的话。

  2. 如果它没有在它的名字这些字之一,那么对象属于别人。如果希望在函数结束后保留对象,则必须调用 [object retain]

如果您在自己创建的函数中也遵循这个约定,那么将会很有帮助。

(吹毛求疵者: 是的,不幸的是有一些 API 调用是这些规则的例外,但是它们很少)。

还有很多关于 cocoadev 的好消息:

和以往一样,当人们开始尝试重新措辞参考资料时,他们几乎总是出错或提供一个不完整的描述。

苹果在 可可的内存管理编程指南中提供了 Cocoa 内存管理系统的完整描述,最后对 内存管理规则有一个简短但准确的概括。

The answers above give clear restatements of what the documentation says; the problem most new people run into is the undocumented cases. For example:

  • Autorelease : 文档说它将在“未来的某个时候”触发一个版本什么时候? !基本上,在将代码退出到系统事件循环之前,可以指望对象一直存在。系统可以在当前事件周期之后的任何时间释放对象。(我想马特早些时候也这么说过。)

  • 静态字符串 : NSString *foo = @"bar";——您必须保留或释放它吗

    -(void)getBar {
    return @"bar";
    }
    

    ...

    NSString *foo = [self getBar]; // still no need to retain or release
    
  • The Creation Rule: If you created it, you own it, and are expected to release it.

In general, the way new Cocoa programmers get messed up is by not understanding which routines return an object with a retainCount > 0.

Here is a snippet from Very Simple Rules For Memory Management In 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

The 1st bullet says: if you called alloc (or new fooCopy), you need to call release on that object.

The 2nd bullet says: if you use a convenience constructor and you need the object to hang around (as with an image to be drawn later), you need to retain (and then later release) it.

The 3rd should be self-explanatory.

IDeveloperTV 网络提供免费视频

Memory Management in Objective-C

除了你可能想花50美元买一本 Hillegass 的书之外,我不会对保留/发布的具体内容进行补充,但是我强烈建议在开发你的应用程序时尽早使用 Instruments 工具(甚至是你的第一个应用程序.为此,请运行-> 从性能工具开始。我会从 Leaks 开始,它只是许多可用的工具中的一个,但是当你忘记释放的时候,它会帮助你看到。你将面对多少信息,这让人望而却步。但是看看下面这个教程,你可以快速起床:
可可教程: 用仪器修复内存泄漏

实际上,尝试 力量泄漏可能是一个更好的方法,反过来,学习如何防止他们!祝你好运;)

我通常收集的 Cocoa 内存管理文章:

可可记忆管理

正如一些人已经提到的,苹果的 Intro to Memory Management是迄今为止最好的起点。

我还没有看到提到的一个有用的链接是 实用内存管理。如果你仔细阅读苹果的文档,你会发现它在文档的中间,但它值得直接链接。这是一个关于内存管理规则的精彩的执行摘要,包括示例和常见错误(基本上这里的其他答案都试图解释,但是没有解释清楚)。