【问题标题】:Calling -retainCount Considered Harmful调用 -retainCount 被认为是有害的
【发布时间】:2011-08-12 15:33:56
【问题描述】:

或者,为什么我没有在暑假使用retainCount

这篇文章旨在征集有关 retainCount 这种臭名昭著的方法的原因和原因的详细文章,以整合围绕 SO 的相关信息。*

  1. 基础知识:不使用retainCount 的官方原因是什么?是否有任何可能有用的情况根本?应该怎么做?** 随意编辑。

  2. 历史/解释:如果 Apple 不打算使用它,为什么要在 NSObject protocol 中提供此方法? Apple 的代码是否出于某种目的依赖 retainCount?如果是这样,为什么不把它藏在某个地方?

  3. 为了更深入地了解:对象的保留计数可能与用户代码中假设的不同的原因是什么?您能否给出框架代码可能使用的标准过程的任何示例***,这些示例会导致这种差异?是否存在保留计数总是与新用户预期不同的已知情况?

  4. 关于retainCount,您认为还有什么值得一提的吗?


* 不熟悉 Objective-C 和 Cocoa 的编码人员经常努力解决或至少误解引用计数方案。教程解释可能会提到保留计数,当您调用 retainalloccopy 等时,保留计数(根据这些解释)会增加一,当您调用 release 时会减少一(在某些情况下)当您拨打autorelease时指向未来。

一个崭露头角的 Cocoa 黑客 Kris,因此很容易得出这样的想法:检查对象的保留计数将有助于解决一些内存问题,而且,你瞧,每个对象都有一个可用的方法,称为 @ 987654334@! Kris 在几个对象上调用retainCount,这个太高了,那个太低了,这到底是怎么回事?!所以 Kris 在 SO 上发了一个帖子,“我的内存管理出了什么问题?”然后一大群 字母下降说“不要那样做!你不能依赖结果。”这很好,但我们勇敢的编码员可能需要更深入的解释。

我希望这会变成一个常见问题解答,一页来自我们任何一位愿意写一篇文章的专家的优秀信息论文/讲座,当他们想知道时,可以指出新的 Cocoa-heads关于retainCount

** 我不想把它说得太宽泛,但是来自经验的具体提示或有关验证/调试保留和释放配对的文档可能适合此处。

***在虚拟代码中;显然,公众无法访问苹果的实际代码。

【问题讨论】:

  • 我发现了这个最近的问题:stackoverflow.com/questions/4636146/when-to-use-retaincount 和 Dave DeLong 的非常有用的答案,但正如我所说,我希望为 retainCount 信息创建一个中心位置(并自己学习一些东西!),特别讨论了retainCount 存在的原因及其无用的示例。不言而喻,但如果您认为这是一个无用的欺骗,请投票关闭,我将删除它!
  • 因为:'你可以在一个类中重写这个方法来实现你自己的引用计数方案。 '
  • +1 用于 Dijkstra 参考。
  • @Stefan:这很好。我希望您稍后会考虑将其扩展为答案(无论多么简短)。
  • @Bavarious:谢谢。不过,这似乎是显而易见的举措。

标签: objective-c cocoa reference-counting retaincount


【解决方案1】:

基础知识:不使用 retainCount 的官方原因是什么?

自动释放管理是最明显的——你无法确定retainCount 表示的引用中有多少在本地或外部(在辅助线程上,或在另一个线程的本地池中)自动释放池.

此外,有些人在泄漏、更高级别的引用计数以及自动释放池如何在基本级别上工作时遇到问题。他们将编写一个程序,而不(太多)考虑正确的引用计数,或者没有正确学习引用计数。这使得他们的程序很难调试、测试和改进——这也是一个非常耗时的修正。

不鼓励使用它(在客户端级别)的原因有两个:

1) 由于多种原因,该值可能会有所不同。 线程本身就足以让人永远不要相信它。

2) 您仍然必须实现正确的引用计数。 retainCount 永远不会让您免于不平衡的引用计数。

是否有任何可能有用的情况?

如果您编写自己的分配器或引用计数方案,或者如果您的对象位于一个线程上并且您可以访问任何 and 它可能存在的所有自动释放池。这也意味着您不会与任何外部 API 共享它。模拟这一点的简单方法是创建一个具有一个线程、零个自动释放池的程序,并以“正常”方式进行引用计数。除了“学术”原因之外,您不太可能需要解决这个问题/编写这个程序。

作为调试辅助:您可以使用它来验证保留计数是否异常高。如果您采用这种方法,请注意实现差异(本文中引用了一些),并且不要依赖它。甚至不要将测试提交到您的 SCM 存储库。

在极少数情况下,这可能是一种有用的诊断方法。可用于检测:

  • 过度保留:如果您的程序可以访问分配,则保留计数正不平衡的分配不会显示为泄漏。

  • 一个被许多其他对象引用的对象:这个问题的一个例子是在多线程上下文中运行的(可变)共享资源或集合 - 频繁访问或更改此资源/集合可能会引入显着程序执行的瓶颈。

  • 自动释放级别:自动释放、自动释放池和保留/自动释放周期都需要成本。如果您需要最小化或减少内存使用和/或增长,您可以使用这种方法来检测过多的案例。

来自 Bavarious 的评论(下):高值也可能表示分配无效(dealloc'd 实例)。这完全是一个实现细节,同样不能在生产代码中使用。启用僵尸时,向此分配发送消息会导致错误。

应该怎么做?

如果您不负责返回self 处的内存(也就是说,您没有编写分配器),请不要理会它 - 它没用。

你必须学习正确的引用计数。

为了更好地理解释放和自动释放的用法,设置一些断点并了解它们是如何使用的,在什么情况下等等。你仍然需要学习正确使用引用计数,但这可以帮助你理解为什么没用。

更简单:使用 Instruments 跟踪分配和引用计数,然后分析活动程序中多个对象的引用计数和调用堆栈。

历史/解释:如果 Apple 不打算使用此方法,为什么它会在 NSObject 协议中提供? Apple 的代码是否出于某种目的依赖于 retainCount?如果是这样,为什么不把它藏在某个地方?

我们可以假设它是公开的,主要有两个原因:

1) 在托管环境中正确的引用计数。分配器可以使用retainCount——真的。这是一个非常简单的概念。在调用-[NSObject release] 时,可能会调用引用计数器(除非被覆盖),如果retainCount 为0(调用dealloc 之后),则可以释放对象。在分配器级别这一切都很好。分配器和区域(很大程度上)是抽象的,所以......这使得结果对普通客户毫无意义。有关为什么 retainCount 在客户端级别不能等于 0、对象释放、释放序列等的详细信息,请参阅 bbum 的评论(如下)。

2) 使其可供想要自定义行为的子类使用,并且因为其他引用计数方法是公共的。在少数情况下它可能很方便,但它通常用于错误的原因(例如不朽的单例)。如果您需要自己的引用计数方案,那么这个系列可能值得覆盖。

为了更深入地了解:对象的保留计数可能与用户代码中假设的不同的原因是什么?您能否给出框架代码可能使用的标准过程的任何示例***,这些示例会导致这种差异?是否存在任何已知的保留计数总是与新用户预期不同的情况?

再次,自定义引用计数方案和不朽对象。 NSCFString 文字属于后一类:

NSLog(@"%qu", [@"MyString" retainCount]); 
// Logs: 1152921504606846975

关于retainCount,您认为还有什么值得一提的吗?

它作为调试辅助是没有用的。学习使用泄漏和僵尸分析,并经常使用它们——即使你掌握了引用计数之后。


更新: bbum 最近发布了一篇题为retainCount is useless 的文章。这篇文章详细讨论了为什么 -retainCount 在绝大多数情况下都没有用。

【讨论】:

  • 注:-retainCount 永远不会归零。
  • 谢谢。这尤其是一个很好的见解:“线程本身就足以让人永远不要相信它。”
  • 不返回零只是一个实现细节,因为返回零需要合理地发送已释放对象的消息(这可能还需要永远不要在同一地址分配另一个对象)。
  • 根据定义,当保留计数变为零时,支持对象的分配被解除分配。该对象的任何后续消息(包括 -retainCount)的行为都未定义。要解决这个问题,您必须使释放后消息传递有效。因此,您永远不能为新对象重复使用任何地址,因为这违反了上述合同(除非您还添加了一些要求,即每个分配都有一个关联的 UUID)。
  • 你当然可以走这条路,但这样做会让你完全脱离 Cocoa/iOS 运行时的现实。但是,它也得到完全支持。如果你愿意,你可以定义你自己的根类,你可以发明任何你想要的分配模式。只是不要将您的对象传递给框架 API! (这实际上是一个非常有趣的心理练习——这些年来我创建了一些根类来探索不同的模型。)
【解决方案2】:

一般的经验法则是,如果您使用这种方法,您最好确定自己知道自己在做什么。如果您使用它来调试内存泄漏,那么您做错了,如果您这样做是为了查看对象发生了什么,那么您做错了。

我曾经使用过一个案例,发现它很有用。那是在做一个共享对象缓存时,我想在没有任何东西引用它时刷新对象。在这种情况下,我等到 retainCount 等于 1,然后我可以释放它,因为我知道没有其他东西在保留它,这显然在垃圾收集环境中无法正常工作,并且有更好的方法来做到这一点。但这仍然是我见过的唯一“有效”用例,而且不是很多人会做的事情。

【讨论】:

  • 你描述的用例是一个非常糟糕的例子——即使在创建缓存时,你也不应该以任何方式使用retainCount。缓存不应该关心它包含的任何对象是否有其他用户 - 它应该在感觉这样做时简单地释放陈旧的对象 - 如果有其他人持有它们,就这样吧。
  • Michal,关键是要尽快驱逐陈旧的对象。对于未来的任何事情,我都会为此目的使用 NSCache,但当时这并不存在。不过,正如我所说,我绝不会向任何人推荐这种做法。
  • 对缓存使用保留计数是错误的。缓存不应该关心其他人是否仍在使用缓存的对象,当最近没有请求对象时,缓存应该将其从自身中逐出。在那里使用 retainCount 来缓存一个对象更长的时间和“关键是尽快驱逐陈旧的对象”是两件不同的事情。
  • 我并不反对在实践中这样做是不好的,但它仍然回答了可以使用 retainCount 的情况,而不是应该在哪里使用它的问题。我认为我们都同意这一点是无处可去的。
  • @Michal:只要对象处于活动使用状态(由 >1 的保留计数表示),将其从缓存中逐出不会释放任何内存。由于有可能再次使用该对象,因此保持引用是明智的。如果对象从缓存中删除,然后在前一个实例被持有它的人释放之前重新缓存,那么您将有一个多余的实例漂浮在周围。
猜你喜欢
  • 2015-09-09
  • 2011-12-28
  • 1970-01-01
  • 2014-11-24
  • 2010-12-22
  • 2010-11-08
  • 1970-01-01
  • 1970-01-01
  • 2010-09-30
相关资源
最近更新 更多