了解Cocoa和Objective-C的引用计数
我只是开始看Objective-C和Cocoa,以期玩iPhone SDK。 我对C的malloc
和free
概念很合适,但Cocoa的引用计数scheme让我颇为困惑。 一旦你了解它,我就会被告知它非常优雅,但是我还没有完成。
如何release
, retain
和autorelease
工作以及有关使用约定是什么?
(或者说失败了,你读了什么帮助你得到了它?)
让我们从retain
和release
开始; 一旦你理解了基本的概念, autorelease
实际上只是一个特例。
在Cocoa中,每个对象跟踪它被引用的次数(具体地说, NSObject
基类实现了这个)。 通过在一个对象上调用retain
,你告诉它你想把它的引用计数加1。 通过调用release
,你告诉你release
的对象,其引用计数递减。 如果在调用release
,引用计数现在为零,则系统释放该对象的内存。
这与malloc
和free
不同的基本方法是任何给定的对象都不需要担心系统崩溃的其他部分,因为你释放了他们正在使用的内存。 假设每个人都在按照规则玩耍和保留/释放,当一段代码保留然后释放该对象时,任何其他代码引用该对象也不会受到影响。
有时可能会引起混淆的是知道你应该retain
和release
。 我的一般经验法则是,如果我想挂在一个对象上一段时间(例如,如果它是一个类中的成员variables),那么我需要确保对象的引用计数知道我。 如上所述,通过调用retain
来增加对象的引用计数。 按照惯例,当用“init”方法创build对象时,它也会增加(设置为1)。 在任何一种情况下,我都有责任在完成对象时调用对象的release
。 如果我不这样做,会有内存泄漏。
对象创build的例子:
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被用作一种方便的(有时是必要的)的方式来告诉系统在一段时间后释放这个对象。 从pipe道的angular度来看,当autorelease
被调用时,当前线程的NSAutoreleasePool
被警告。 NSAutoreleasePool
现在知道,一旦它获得了一个机会(在事件循环的当前迭代之后),它可以调用对象上的release
。 从我们作为程序员的angular度来看,它会为我们调用release
,所以我们不必(实际上也不应该)。
需要注意的是(同样按照惯例),所有的对象创build类方法都会返回一个自动释放对象。 例如,在以下示例中,variables“s”的引用计数为1,但在事件循环完成后,将被销毁。
NSString* s = [NSString stringWithString:@"Hello World"];
如果你想挂在那个string上,你需要明确地调用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]; }
我意识到所有这一切都有点令人困惑 – 不过在某些时候,它会点击。 这里有几个引用让你走:
- 苹果介绍内存pipe理。
- cocoa编程为Mac OS X(第四版) ,由亚伦Hillegas – 一个很好的书面,有很多很好的例子。 它看起来像一个教程。
- 如果你真的潜入,你可以前往大书呆子牧场 。 这是由上述书的作者Aaron Hillegas经营的培训机构。 我几年前参加了cocoa课程,这是一个很好的学习方式。
如果你理解保留/释放的过程,那么对于已经build立的Cocoa程序员来说,有两条明显的“黄金”规则是很明显的,但不幸的是很less有人为新手清楚地说明了这一点。
-
如果一个返回一个对象的
alloc
有alloc
,create
或者copy
它的名字,那么这个对象就是你的。 完成后必须调用[object release]
。 或CFRelease(object)
,如果它是一个核心基础对象。 -
如果它的名字中没有这些单词中的一个,那么该对象属于其他人。 如果希望在函数结束后保留对象,则必须调用
[object retain]
。
你会很好地服从你自己创造的function遵循这个约定。
(Nitpickers:是的,不幸的是有几个API调用是这些规则的例外,但是很less见)。
如果您正在为桌面编写代码,并且您可以定位Mac OS X 10.5,那么至less应该使用Objective-C垃圾收集。 这实际上将简化您的大部分开发工作 – 这就是为什么苹果将所有的努力放在首位,并使其performance良好。
至于不使用GC的内存pipe理规则:
- 如果使用
+alloc/+allocWithZone:
,+new
,-copy
或-mutableCopy
创build一个新对象,或者如果您-mutableCopy
一个对象,那么您将拥有它,并且必须确保它被发送。 - 如果您以其他方式收到物品,则您不是该物品的所有者, 也不应确保将其发送。
- 如果你想确保一个对象被发送,你可以自己发送,或者你可以发送对象的自动释放 ,当前的自动释放池将发送它 –
-release
(一旦收到-autorelease
)当池被耗尽。
通常, -autorelease
用于确保对象在当前事件的长度-autorelease
活,但之后会被清理,因为有一个围绕cocoa事件处理的自动释放池。 在Cocoa中,将对象返回给autoreleased的调用者比返callback用者本身需要释放的objets要常见得多。
Objective-C使用引用计数 ,这意味着每个对象都有一个引用计数。 创build对象时,引用计数为“1”。 简单地说,当一个对象被引用(即存储在某处)时,它被“保留”,这意味着其引用计数增加1。 当一个对象不再需要时,它被“释放”,这意味着其引用计数减1。
当一个对象的引用计数为0时,该对象被释放。 这是基本的参考计数。
对于某些语言,引用会自动增加和减less,但是objective-c不是这些语言之一。 因此程序员负责保留和释放。
编写一个方法的典型方法是:
id myVar = [someObject someMessage]; .... do something ....; [myVar release]; return someValue;
需要记住在代码中释放任何获得的资源的问题既乏味又容易出错。 Objective-C引入了另一个旨在使这更容易的概念:自动释放池。 自动释放池是安装在每个线程上的特殊对象。 他们是一个相当简单的类,如果你查找NSAutoreleasePool。
当一个对象得到一个“autorelease”消息发送给它,对象将寻找任何autorelease池坐在堆栈上的当前线程。 它将把对象添加到列表中作为一个对象,在将来的某个时刻发送“释放”消息,这通常是在释放池本身时。
以上面的代码,你可以重写它更短,更容易阅读说:
id myVar = [[someObject someMessage] autorelease]; ... do something ...; return someValue;
因为对象是自动释放的,所以我们不再需要显式地调用“释放”。 这是因为我们知道一些自动释放池将在稍后为我们做。
希望这有助于。 维基百科的文章对于引用计数是相当不错的。 有关autorelease池的更多信息可以在这里find 。 另外请注意,如果您正在为Mac OS X 10.5及更高版本构build,则可以告诉Xcode使用垃圾回收进行构build,从而完全忽略保留/释放/自动释放。
Joshua(#6591) – Mac OS X 10.5中的垃圾收集器看起来很酷,但是不适用于iPhone(或者如果您希望您的应用程序在Mac OS X的10.5之前的版本上运行)。
另外,如果你正在编写一个库或者可能被重用的东西,那么使用GC模式将任何使用代码的人都locking在GC模式下,据我所知,任何试图编写广泛可重用代码的人都倾向于pipe理手动记忆。
与往常一样,当人们开始尝试重新参考参考资料时,他们几乎总是出错或提供不完整的描述。
在Cocoa的“内存pipe理编程指南”中, Apple提供了对Cocoa内存pipe理系统的完整描述,最后简要但精确地总结了内存pipe理规则 。
除了你可能想要考虑放弃$ 50并获得Hillegass书籍之外,我不会添加保留/发行版的具体内容,但是我强烈build议在应用程序开发的早期就开始使用Instruments工具(甚至是你的第一!)。 为此,请运行 – >从性能工具开始。 我会从Leaks开始,这只是许多可用的工具之一,但是当你忘记发布时会帮助你展示。 这是放弃了多less信息,你会被提出。 但看看这个教程快速起床:
cocoa教程:用仪器固定存储器泄漏
其实试图强制泄漏可能是一个更好的方法,反过来,学习如何防止他们! 祝你好运 ;)
马特·迪拉德写道 :
返回[[s autorelease]释放];
自动释放不保留该对象。 自动释放只是把它放在队列中待以后发布。 你不想在那里有一个发布声明。
iDeveloperTV Network提供免费的截屏
Objective-C中的内存pipe理
我通常收集的cocoa内存pipe理文章:
cocoa内存pipe理
NilObject的答案是一个好的开始。 以下是有关手动内存pipe理的补充信息( iPhone上需要 )。
如果你自己alloc/init
一个对象,它带有一个引用计数1.当它不再需要时,你可以通过调用[foo release]
或者[foo autorelease]
来负责清理它。 release会马上清除它,而autorelease则会将这个对象添加到autorelease池中,这个池会在稍后自动释放它。
autorelease主要用于当你有一个方法需要返回有问题的对象( 所以你不能手动释放它,否则你将返回一个零对象 ),但你不想坚持它,要么。
如果你获得了一个你没有调用alloc / init的对象来获取它 – 例如:
foo = [NSString stringWithString:@"hello"];
但是你想挂在这个对象上,你需要调用[foo retain]。 否则,它可能会得到autoreleased
,你会坚持一个零引用(就像在上面的stringWithString
例子 )。 当你不再需要它时,调用[foo release]
。
上面的答案明确说明了文件的内容。 大多数新人遇到的问题是无证件的情况。 例如:
-
Autorelease :docs表示它将在未来的某个时候触发一个版本。 什么时候?! 基本上,你可以指望对象在你退出到系统事件循环的代码。 系统可以在当前事件循环之后的任何时候释放对象。 (我认为马特早些时候说过。)
-
静态string :
NSString *foo = @"bar";
– 你必须保留或释放? 不怎么样-(void)getBar { return @"bar"; }
…
NSString *foo = [self getBar]; // still no need to retain or release
-
创build规则 :如果你创build了它,你拥有它,并且有望释放它。
一般来说,新Cocoa程序员弄乱的方式是不理解哪个例程返回一个retainCount > 0
的对象。
这里是一个非常简单的规则cocoa内存pipe理的片段:
保留计数规则
- 在给定的块内,使用-copy,-alloc和-retain应该等于使用-release和-autorelease。
- 使用便利构造函数创build的对象(例如NSString的stringWithString)被认为是自释放的。
- 实现一个-dealloc方法来释放你拥有的实例variables
第一个项目符号说:如果你调用alloc
(或new fooCopy
),你需要调用该对象的释放。
第二个项目符号说:如果你使用了一个便利的构造函数, 并且你需要这个对象 (在后面要绘制的图像中),你需要保留(然后再发布)它。
第三个应该是不言自明的。
很多有关cocoadev的信息:
- 内存pipe理
- 经验法则
正如几位人士所提到的,苹果公司的内存pipe理介绍是迄今为止最好的开始。
我还没有见到的一个有用的链接是实用内存pipe理 。 如果您仔细阅读,可以在苹果的文档中find它,但值得直接链接。 这是对内存pipe理规则的一个精彩的执行摘要,带有例子和常见的错误(基本上还有其他的答案是要解释的,但不是那么好)。