了解 Cocoa 和 Objective-C 的引用计数
-
08-06-2019 - |
题
我刚刚开始了解 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
当我完成它时,在物体上。如果不这样做,就会出现内存泄漏。
对象创建示例:
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];
}
我意识到所有这一切都有点令人困惑 - 但在某些时候,它会点击。以下是一些可以帮助您继续前进的参考:
- 苹果的介绍 到内存管理。
- Mac OS X 的 Cocoa 编程(第四版), ,作者 Aaron Hillegas - 一本写得很好的书,有很多很棒的例子。它读起来就像一个教程。
- 如果您真的想深入了解,您可以前往 大书呆子牧场. 。这是由上述书籍的作者 Aaron Hillegas 经营的培训机构。几年前我参加了那里的可可简介课程,这是一种很好的学习方式。
其他提示
如果您了解保留/释放的过程,那么有两条黄金法则对于成熟的 Cocoa 程序员来说是显而易见的,但不幸的是,对于新手来说很少清楚地阐明这一点。
如果一个返回对象的函数有
alloc
,create
或者copy
根据其名称,该对象就是您的。你必须打电话[object release]
当你完成它时。或者CFRelease(object)
, ,如果它是 Core-Foundation 对象。如果其名称中没有这些单词之一,则该对象属于其他人。你必须打电话
[object retain]
如果您希望在函数结束后保留该对象。
如果您在自己创建的函数中也遵循此约定,将会得到很好的帮助。
(挑剔者:是的,不幸的是,有一些 API 调用是这些规则的例外,但这种情况很少见)。
如果您正在为桌面编写代码并且可以以 Mac OS X 10.5 为目标,那么您至少应该考虑使用 Objective-C 垃圾收集。它确实会简化你的大部分开发工作——这就是为什么苹果一开始就投入所有精力来创建它,并使其表现良好。
至于不使用GC时的内存管理规则:
- 如果您使用创建一个新对象
+alloc/+allocWithZone:
,+new
,-copy
或者-mutableCopy
或者如果你-retain
一个对象,您拥有它的所有权并且必须确保它被发送-release
. - 如果您以任何其他方式收到物品,则您 不是 它的所有者并且应该 不是 确保已发送
-release
. - 如果您想确保对象已发送
-release
您可以自己发送,也可以发送对象-autorelease
和当前的 自动释放池 将发送它-release
(每收到一次-autorelease
)当水池排空时。
通常 -autorelease
被用来确保对象在当前事件的持续时间内存活,但之后会被清理,因为有一个围绕 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;
因为该对象是自动释放的,所以我们不再需要对其显式调用“release”。这是因为我们知道一些自动释放池稍后会为我们做这件事。
希望这有帮助。维基百科的文章关于引用计数非常好。有关更多信息 自动释放池可以在这里找到. 。另请注意,如果您正在为 Mac OS X 10.5 及更高版本进行构建,您可以告诉 Xcode 在启用垃圾收集的情况下进行构建,从而允许您完全忽略保留/释放/自动释放。
Joshua (#6591) - Mac OS X 10.5 中的垃圾收集功能看起来很酷,但不适用于 iPhone(或者如果您希望您的应用程序在 Mac OS X 10.5 之前的版本上运行)。
另外,如果您正在编写一个库或可能重用的东西,那么使用 GC 模式会锁定使用该代码的任何人也使用 GC 模式,所以据我了解,任何试图编写广泛可重用代码的人都倾向于去管理手动记忆。
一如既往,当人们开始尝试重新措辞参考材料时,他们几乎总是会出错或提供不完整的描述。
Apple 在中提供了 Cocoa 内存管理系统的完整描述 Cocoa 内存管理编程指南, ,最后有一个简短但准确的总结 内存管理规则.
我不会添加保留/释放的具体内容,除非您可能想考虑花 50 美元并获得 Hillegass 书,但我强烈建议您在应用程序开发的早期就开始使用 Instruments 工具(甚至是您的应用程序)。第一!)。为此,请运行->使用性能工具启动。我将从 Leaks 开始,它只是众多可用工具之一,但将有助于在您忘记发布时向您展示。您将看到多少信息,这真是令人畏惧。但请查看本教程以快速入门:
可可教程:使用工具修复内存泄漏
其实想 力量 反过来,泄漏可能是学习如何预防泄漏的更好方法!祝你好运 ;)
return [[s autorelease] 释放];
自动释放确实 不是 保留该对象。自动释放只是将其放入队列中以供稍后释放。您不想在那里有发布声明。
我平时收藏的Cocoa内存管理文章:
iDeveloperTV Network 提供免费截屏视频
NilObject 的回答是一个好的开始。以下是一些有关手动内存管理的补充信息(iPhone 上需要).
如果你个人 alloc/init
一个对象,它的引用计数为 1。当不再需要它时,您有责任通过调用来清理它 [foo release]
或者 [foo autorelease]
. 。release 会立即清理它,而 autorelease 将对象添加到自动释放池中,稍后会自动释放它。
autorelease 主要适用于当您有一个方法需要返回有问题的对象时(所以你不能手动释放它,否则你将返回一个 nil 对象)但你也不想坚持下去。
如果您获取了一个没有调用 alloc/init 来获取的对象 - 例如:
foo = [NSString stringWithString:@"hello"];
但你想挂起这个对象,你需要调用[fooretain]。否则,有可能会得到 autoreleased
你将保留一个零引用 (就像上面的那样 stringWithString
例子)。当您不再需要时,请致电 [foo release]
.
上面的答案清楚地重述了文档的内容;大多数新人遇到的问题是无证案件。例如:
自动释放: :Docs说,它将触发“将来的某个时候”。什么时候?!基本上,您可以依赖该对象,直到您将代码退出到系统事件循环中为止。系统可以在当前事件周期之后的任何时间释放该对象。(我想马特早些时候说过。)
静态字符串:
NSString *foo = @"bar";
——你必须保留还是释放它?不。怎么样-(void)getBar { return @"bar"; }
...
NSString *foo = [self getBar]; // still no need to retain or release
创造规则: :如果你创建了它,你就拥有它,并且应该释放它。
一般来说,新的 Cocoa 程序员陷入困境的方式是不理解哪些例程返回一个带有 retainCount > 0
.
这是来自的一个片段 Cocoa 中内存管理的非常简单的规则:
保留计数规则
- 在给定块内,-copy、-alloc 和 -retain 的使用应等于 -release 和 -autorelease 的使用。
- 使用便利构造函数创建的对象(例如NSString 的 stringWithString) 被认为是自动释放的。
- 实现 -dealloc 方法来释放您拥有的实例变量
第 1 颗子弹说:如果你打电话 alloc
(或者 new fooCopy
),您需要对该对象调用release。
第二条子弹说:如果你使用方便的构造函数 你需要这个物体在周围闲逛 (就像稍后绘制的图像一样),您需要保留(然后释放)它。
第三个应该是不言自明的。