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