【问题标题】:Conflict between memory management descriptions in ObjC book and official docsObjC 书籍和官方文档中内存管理描述的冲突
【发布时间】:2012-05-26 18:51:22
【问题描述】:

我正在尝试了解/了解在使用或创建各种对象时会发生什么以及为什么。 (希望从文档中学习。)

我正在阅读“Objective-C 2.0 中的编程”(第 2 版,作者 Steven Kochan)。在第 408 页,第一段讨论了保留计数:

请注意,它的引用计数会变为 2。addObject: 方法会自动执行此操作;如果您检查文档中的 addObject: 方法,您会看到那里描述了这一事实。

所以我阅读了addObject: 文档:

在数组末尾插入给定对象。

那里,没有描述,而其他项目,如arrayByAddingObject:说明

返回一个新数组,它是接收数组的副本,末尾添加了给定对象。

在参考文献中的什么地方表明addObject: 增加了保留计数?鉴于 ARC 的存在,我仍然应该了解这些方法正在做什么来避免错误和问题。 ARC为此带来了什么? (要再读一遍……)

【问题讨论】:

  • 我更感兴趣的是了解这些概念的编写位置,而不是解决有关数组方法的问题。我希望能够“参考文件”以拼凑出此类问题的答案……爱因斯坦给出了一个很好的答案。 我在哪里可以找到处理更复杂对象的答案
  • 任何以绝对值讨论保留计数的书都应该被认为是可疑的。
  • @bbum 这本书和许多书一样,试图解释内存管理的微妙之处,并用一个例子来说明各种方法发生后会发生什么。很难接触到每个读者。
  • 是的——这是一个糟糕的例子;它实际上是在教导学生保留计数的实际值有某种绝对的、可预测的含义。虽然这在非常简单的情况下有效,但一旦你做任何真实的事情,它就会完全崩溃——学生在没有工具来处理现实的情况下感到困惑。保留计数应视为增量;它会增加和减少,你保持平衡,一切都很好。
  • @David:如果您认为我的标题编辑误解了您的意思,请按照您的喜好重新编辑。我只是想让它更明确——最初它是模糊/广泛的。诸如“我如何确定 Cocoa 方法的内存管理含义?”之类的东西。会比你原来的标题好一点。

标签: objective-c cocoa memory-management


【解决方案1】:

很好的问题,我很高兴看到有人真正阅读文档并试图理解它们

由于您正在寻找如何使用 Apple 文档而不是实际答案本身来研究答案,因此我是这样找到答案的:

  • 首先我查看addObject: 的类引用,它是NSMutableArray 的一个方法,并且没有提到内存管理。
  • 然后我看看顶部的Overview部分......嗯,还是没有运气。
  • 由于行为可能继承自父类,因此我查看了类引用顶部的 Inherits from 部分,发现NSArray 是最直接的父类。让我们检查一下:
  • 概述下有一小部分是关于保留的:

特别注意事项

在大多数情况下,您的自定义 NSArray 类应该符合 Cocoa 的 对象所有权约定。因此,您必须将保留发送到每个对象 您添加到您的收藏并发布到您的每个对象 从集合中删除。当然,如果子类化的原因 NSArray 是为了实现不同于 norm (例如,一个非保留数组),那么你可以忽略这个 要求。

  • 好吧,我还是不开心……接下来呢? NSArray 的父类是 NSObject,我知道在这种情况下它不会被覆盖(根据经验),所以我不会费心检查它。 (如果父类是另一个类或可能被 NSObject 覆盖的东西,我会继续向上移动,直到找到东西。)
  • 配套指南通常包含有关这些类型课程的大量有用信息。让我们试试第一个,Collections Programming Topics
  • 第一部分(在概述之后)是访问索引和轻松枚举元素:数组。听起来很有希望!点击相关章节:“数组:有序集合”
  • 它位于 Array Fundamentals 下,以及指向更多信息的链接:

当你将一个对象添加到一个 NSMutableArray 对象时,该对象 不会被复制,(除非您将 YES 作为参数传递给 initWithArray:copyItems:)。而是直接将对象添加到 大批。在托管内存环境中,对象接收保留 添加时的消息;在垃圾收集环境中,它是 强烈引用。在托管内存中释放数组时 环境中,每个元素都被发送一个释放消息。更多 有关复制和内存管理的信息,请参阅“复制 收藏。”

【讨论】:

  • @inafziger 很好的答案。核心在第一个项目符号列表中。 (我们的想法必须一致。)
  • @DavidC:很高兴我能帮助另一个佛罗里达飞行员! (或者真的,任何人,但这是一个奖励。:))
  • 嗯,我想结论是文档不是确定信息的第一来源。想象一下,为我们在代码中编写的方法这样做??
  • 嗯,它(埋在文档中)......他们只是不容易!
  • 很好的答案,感谢文档中的具体引用!我认为这是关于 SO 的唯一问题,其中有比 bbum(上图)更好的答案。
【解决方案2】:

这本书一定是指过时的文档,因为你是对的,它没有提到任何关于保留计数的内容。它实际上保留了该对象。您需要考虑的方式不是保留计数(这是无用的),而是所有权。尤其是在使用 ARC 时。

当您将对象添加到 NSMutableArray 时,它会获得该对象的所有权(在 ARC 术语中,它具有对它的强引用)。

“ARC 带来了什么?”

ARC 没有什么不同。 ARC 所做的所有事情(除了一些优化)都是添加相同的 release、retain 和 autorelease 语句,这些语句与您在不使用 ARC 的情况下自己添加的语句相同。您只需要关心的是,一旦您将一个对象添加到数组中,它的寿命将至少与数组一样长。


arrayByAddingObject: 方法会创建一个新的NSArray(或NSMutableArray),其中包含您要传递的对象,并保持对传递对象的强引用。它创建的实际数组对象还没有引用,除非您将它分配给 ivar、属性或局部变量。你分配给它的东西决定了它的寿命。


基本上,即使没有 ARC,最好从所有权的角度来考虑对象生命周期,ARC 只是将其形式化。因此,因此,在使用框架时,无论何时发生或不发生保留都无关紧要,您只需对您的对象负责,直到您将所有权传递给另一个对象,并且您可以相信框架将使对象保持活动状态只要它需要。

当然,现在您必须凭直觉知道什么是所有权。例如,委托属性通常是 assign,或者在 ARC 中 unsafe_unretainedweak,以防止循环保留循环(两个对象相互保留),尽管有时保留/强,所以你需要查看那些逐案处理。

而且在像键值观察和 NSNotification 观察你正在观察的对象这样的情况下也不会保留观察者。

但这些确实是规则的例外。一般来说,你可以假设一个强参考。

关于上面这句话:“它创建的实际数组对象还没有引用,除非您将其分配给 ivar、属性或局部变量。您分配给它的内容决定了它的生命周期。”我会尽力解释:

当您运行这段代码时:[someArray arrayByAddingObject:someObject]; 您已经实例化了一个新的 NSArrayNSMutableArray 对象(取决于 someArray 是哪个对象类型),但您实际上并没有将它分配给任何引用.这意味着如果你使用 ARC,它可能会在之后立即释放,或者如果不使用 ARC,它会在它的 autoreleasepool 耗尽时被释放(可能在该线程的 runloop 的下一次迭代中)。

现在,如果您这样做:NSArray *someOtherArray = [someArray arrayByAddingObject:someObject]; 您现在有一个对新创建的数组的引用,称为 someOtherArray。在这种情况下,这是一个局部变量,其范围仅在它所在的 { } 的任何一个集合内(因此它可能在 if 语句、循环或方法内。现在,如果你什么都不做有了它,它会在作用域结束后的某个时间死掉(不能保证它会立即死掉,但这并不重要,你不能假设它的寿命更长)。

现在,如果在您的班级中,您在标头中声明了一个 iVar(实例变量),例如 NSArray *someOtherArray;(在 ARC 中默认为强),并且您在班级的某处运行 someOtherArray = [someArray arrayByAddingObject:someObject];,该对象将一直存在,直到您要么删除引用 (someOtherArray = nil),要么覆盖引用 (someOtherArray = someThirdArray),要么释放类。如果您没有使用 ARC,则必须确保保留它以达到相同的效果(someOtherArray = [[someArray arrayByAddingObject:someObject] retain]; 这基本上是 ARC 在幕后所做的)。

或者您可能声明了一个属性,例如 @property (nonatomic, strong) NSArray *someOtherArray,其中 self.someOtherArray = [someArray arrayByAddingObject:someObject]; 将实现相同的效果,但会使用属性访问器 (setSomeOtherArray:),或者您仍然可以使用 someOtherArray = [someArray arrayByAddingObject:someObject]; 直接设置 iVar (假设你@synthesized它)。

或者假设非 ARC,您可能已经声明了类似 @property (nonatomic, retain) NSArray *someOtherArray 的属性,其中 self.someOtherArray = [someArray arrayByAddingObject:someObject]; 的行为与 ARC 完全相同,但是当直接设置 iVar 时,您仍然需要手动添加该保留。

我希望这能澄清一点,如果有什么我掩盖或遗漏的地方,请告诉我。


正如您在评论中提到的,这里的关键是直观地知道一个对象何时会被视为由另一个对象拥有。幸运的是,Cocoa 框架遵循一套非常严格的约定,让您可以做出安全的假设:

  • 在设置框架对象的NSString 属性(例如UILabeltext 属性)时,它总是被复制(如果有人知道反例,请评论或编辑)。因此,一旦通过它,您就不必担心您的字符串。复制字符串以防止可变字符串在传递后被更改。
  • 设置delegate 以外的任何其他属性时,它(几乎?)始终保留(或在 ARC 中为强引用)
  • 在设置委托属性时,它(几乎?)总是一个分配(或弱引用)来防止循环保留循环。 (例如,对象a 有一个属性b 是强引用的,b 有一个强引用的委托属性。您将a 设置为b 的委托。现在ab都相互强引用,并且两个对象都不会达到 0 的保留计数,也永远不会达到它的 dealloc 方法来释放另一个对象。NSURLConnection 是一个强烈引用它的委托的反例,因为它的委托是通过一种方法设置——参见下面的约定——并且约定在完成后消除或释放NSURLConnection,而不是在dealloc中,这将删除循环保留)
  • 添加到数组或字典时,始终保留(或强引用)。
  • 在调用方法和传递块时,它们总是被复制以将它们从堆栈(最初出于性能目的而创建的地方)移动到堆中。
  • 接受对象参数并且不立即返回结果的方法是(总是?我想不出任何不)复制或保留(强引用)您传递的参数以确保方法可以对它们做它需要做的事情。例如,NSURLConnection 甚至保留它的委托,因为它是通过方法传入的,而在设置委托时其他对象的 property 将不会保留,因为这是约定。

建议您在自己的课程中也遵循这些相同的约定,以保持一致性。

此外,不要忘记所有类的标题都可供您使用,因此您可以轻松查看属性是保留还是分配(或强或弱)。您无法检查方法对其参数的作用,但由于参数归接收者所有的约定,因此没有必要。

【讨论】:

  • 呃。很好的答案(对我来说),直到低于线。感谢您将问题(和阅读)指向“强弱”参考。
  • 你能解释一下你发现行下面的部分有什么问题吗?
  • 还要澄清最后一部分,新创建的数组保持对其中所有对象的强引用,因此它对第一个数组中的所有对象以及传递给arrayByAddingObject: 的附加对象。
  • 我没有说“错”。请记住,我想了解文档提供的内容。它是新的和令人困惑的,因为:'并保持对传递对象的强引用。它创建的实际数组对象还没有引用......' 我仍在努力解决这个问题。
  • 刚刚在我的答案中添加了一堆,希望能澄清一点
【解决方案3】:

一般来说,您应该在“最全球化”的地方查找有关 Cocoa API 中任何内容的信息。由于内存管理在系统 API 中普遍存在并且这些 API 在 Cocoa 内存管理策略的实现中是一致的,您只需阅读和理解 Cocoa 内存管理指南。

一旦理解,您可以放心地假设所有系统 API 都实现了该内存管理策略,除非另有明确说明

因此,对于 NSMutableArray 的 addObject: 方法,它必须 retain 将对象添加到数组中,否则将违反该标准策略。

您会在整个文档中看到这一点。这可以防止每个方法的文档变成一页或更长时间并且当罕见的方法或类实现某些东西时,无论出于何种原因(有时不太好),规则的例外情况都很明显.


在内存管理指南的“基本内存管理规则”部分:

您可以使用保留取得对象的所有权。

接收到的对象通常保证在 方法,并且该方法也可以安全地返回 对其调用者的对象。您在两种情况下使用保留:​​(1)在 访问器方法或初始化方法的实现,采取 您想要存储为属性值的对象的所有权; (2) 防止对象因某些副作用而无效 其他操作(如“避免导致重新分配 您正在使用的对象”)。

(2) 是关键; NS{Mutable}Array 必须 retain 任何添加的对象,因为它需要防止添加的对象因某些副作用而失效。不这样做将与上述规则背道而驰,因此将被明确记录。

【讨论】:

  • 简洁地说。 Cocoa 内存管理指南 看起来像要读取(集成)和摘要的 对象
  • 尽我所能,找到你的建议,支持一句话回避我:'因此,对于 NSMutableArray 的 addObject: 方法,它必须保留添加到数组中的对象,否则它将违反该标准政策。使用 [developer.apple.com/library/ios/#documentation/CoreFoundation/… ](内存管理编程指南 - 所有权政策),我找不到(例如)addObject: 和“必须保留”之间的链接。建议??
  • @DavidC bbum 有很好的建议,但是您应该查看高级内存管理指南,因为它涵盖了可可对象,而不是您提供的核心链接基础对象。请参阅:developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/… 请参阅内存管理政策(第 2 点)和实用内存管理,其中包含更详细的集合。
  • @inafziger & bbum iOS 库中的这个高级内存管理编程指南确实是找到这个问题的答案的地方。作为旁注,它们没有突出或关注对象所有权的概念,可在 Memory Management Policy 中找到,而且,数组示例在 Practical Memory Management中非常响亮>
猜你喜欢
  • 2011-10-13
  • 1970-01-01
  • 2020-01-11
  • 2021-12-17
  • 1970-01-01
  • 2011-07-26
  • 1970-01-01
  • 2022-08-24
  • 1970-01-01
相关资源
最近更新 更多