ObjC书中的内存管理描述与官方文档之间的冲突

Dav*_*vid 3 cocoa memory-management objective-c

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

我正在阅读"Objective-C 2.0编程"(Steven Kochan的第2版).在页408,第一段是保留计数的讨论:

请注意,它的引用计数然后转到2.该addObject:方法自动执行此操作; 如果你检查你的文档的addObject:方法,你会看到这里描述的事实.

所以我读了addObject:文档:

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

在那里,缺少描述,而其他项目,如arrayByAddingObject:,声明:

返回一个新数组,该数组是接收数组的副本,并将给定对象添加到结尾.

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

lna*_*ger 7

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

既然您正在寻找如何使用Apple的文档来研究答案,而不是实际答案本身,那么我就是这样找到答案的:

  • 首先,我看一下类引用,addObject:它是一种方法,NSMutableArray并没有提到内存管理.
  • 然后我看看顶部的概述部分...嗯,仍然没有运气.
  • 由于行为可能是从父类继承的,因此我会查看类引用顶部的" 继承自"部分,看看它NSArray是最直接的父级.我们来看看:
  • 概述下有一个关于保留的小部分:

特别注意事项

在大多数情况下,您的自定义NSArray类应符合Cocoa的对象所有权约定.因此,您必须将retain添加到您添加到集合中的每个对象,并释放到从集合中删除的每个对象.当然,如果继承NSArray的原因是为了实现与规范不同的对象保留行为(例如,非保留数组),那么您可以忽略此要求.

  • 好吧,我还是不开心......下一个是哪里?我的父类NSArrayNSObject,我知道在这种情况下它不会被覆盖(来自经验),所以我不打算检查它.(如果父母是另一个类或可能被NSObject覆盖的东西,我会继续向上移动树,直到我找到了什么.)
  • 伴侣指南通常包含了很多很好的信息,这些类型的类.我们来试试第一个,收藏编程主题.
  • 第一部分(概述之后)是访问索引和轻松枚举元素:数组.听起来很有希望!单击Relevant Chapters:"Arrays:Ordered Collections"
  • 它位于Array Fundamentals下,并提供了更多信息的链接:

当您将对象添加到NSMutableArray对象时,不会复制该对象(除非您将YES作为参数传递给initWithArray:copyItems :).而是将对象直接添加到数组中.在托管内存环境中,对象在添加时会收到保留消息; 在垃圾收集环境中,它被强烈引用.在托管内存环境中取消分配数组时,会向每个元素发送一条释放消息.有关复制和内存管理的详细信息,请参阅"复制集合".


Ben*_*ron 6

这本书必须提到过时的文档,因为你是正确的,它没有提到有关保留计数的任何内容.事实上它确实保留了对象.你需要考虑它的方式不是保留计数(这是无用的)而是所有权.特别是在使用ARC时.

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

"ARC为此带来了什么?"

ARC没有什么不同.所有ARC(除了一些优化之外)都会添加相同的release,retain和autorelease语句,您可以在不使用ARC的情况下自行添加.您需要关心的是,一旦将对象添加到数组中,它将至少与数组一样长.


并且该arrayByAddingObject:方法创建一个包含您传递的对象的新NSArray(或NSMutableArray),并保持对传递的对象的强引用.除非您将其分配给ivar,property或local变量,否则它创建的实际数组对象尚未引用.您分配的内容决定了它的使用寿命.


基本上即使没有ARC,最好根据所有权来考虑对象生命周期,ARC只是将其正式化.因此,在使用框架时,保留发生或不发生并不重要,在将所有权传递给另一个对象之前,您只负责对象,并且您可以相信框架将使对象保持活动状态只要它需要它.

当然,你必须知道什么是所有权.例如委托特性通常assign,或者在ARC unsafe_unretainedweak,为防止圆形保持周期(其中,每个的两个对象保持彼此),虽然有时保留/强,所以你需要逐个寻找到那些上的情况.

而且在键值观察和NSNotification等情况下,观察您观察的对象并不会保留观察者.

但这些确实是规则的例外.通常,您可以假设一个强大的参考.

关于上面这句话:"它创建的实际数组对象还没有引用,除非你将它分配给ivar,property或local变量.你赋予它的确定它的生命周期." 我会试着解释一下:

当你运行这段代码:[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];可以达到相同的效果但是会使用proprety accessor(setSomeOtherArray:),或者你仍然可以someOtherArray = [someArray arrayByAddingObject:someObject];用来直接设置iVar(假设你@synthesized).

或取非ARC,你可能宣布类似的财产@property (nonatomic, retain) NSArray *someOtherArrayself.someOtherArray = [someArray arrayByAddingObject:someObject];会表现得完全一样ARC会,但设置伊娃时,直接你仍然需要添加手动保留.

我希望稍微澄清一下,如果有任何我掩饰或遗漏的东西,请告诉我.


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

  • 当设置NSString框架对象的text属性(UILabel例如,例如属性)时,它总是被复制(如果有人知道反例,请注释或编辑).因此,一旦传递它,您就不必担心它的字符串.复制字符串以防止可变字符串在传递后被更改.
  • 当设置除了以外的任何其他属性时delegate,它(几乎?)始终保留(或ARC中的强引用)
  • 设置委托属性时,它(几乎?)总是指定(或弱引用)以防止循环保留循环.(例如,对象a有一个b强引用的属性,并且b具有强引用的委托属性.您设置a为委托b.现在a并且b两者都强烈引用彼此,并且任何对象都不会达到保留计数0并且永远不会达到它的dealloc方法来释放另一个对象.NSURLConnection是一个反例,它强烈引用它的委托,因为它的委托是通过一个方法设置的 - 参见下面的约定 - 并且它的约定是NSURLConnection在它完成后nil out或release a 而不是in dealloc,这将取消通函保留)
  • 添加到数组或字典时,它始终保留(或强引用).
  • 在调用方法并传递块时,它们总是被复制以将它们从堆栈(最初为了性能目的而创建它们)移动到堆中.
  • 接收对象参数并且不立即返回结果的方法(总是?我不能想到任何不这样做)复制或保留(强引用)您传递的参数以确保方法可以执行他们需要什么.例如,NSURLConnection甚至保留它的委托,因为它是通过方法传入的,而当设置其他对象的委托属性时将不会保留,因为这是约定.

建议您在自己的类中遵循这些相同的约定以保持一致性.

另外,不要忘记所有类的标题都可供您使用,因此您可以轻松查看属性是保留还是分配(或强或弱).您无法检查哪些方法对其参数有效,但由于参数由接收方拥有,因此不需要.

  • 另外为了澄清最后一部分,新创建的数组保留了对其中所有对象的强引用,因此它强烈引用第一个数组中的所有对象以及传递给`arrayByAddingObject的附加对象:`. (2认同)

bbu*_*bum 5

通常,您应该在"最全局"的位置查看有关Cocoa API中任何内容的信息.由于内存管理在整个系统API中普遍存在,并且 API在Cocoa内存管理策略的实现中是一致的,因此您只需阅读并理解Cocoa内存管理指南.

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

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

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


在内存管理指南的"基本内存管理规则"部分中:

您可以使用retain获取对象的所有权.

通常保证接收到的对象在接收到的方法中保持有效,并且该方法也可以安全地将对象返回给其调用者.在两种情况下使用retain:(1)在accessor方法或init方法的实现中,获取要存储为对象属性的对象的所有权; (2)防止对象因某些其他操作的副作用而失效(如"避免导致重新分配您正在使用的对象"中所述).

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

  • @DavidC bbum有很好的建议,但你应该看**高级内存管理指南**,因为它涵盖了cocoa对象,而不是你提供的核心基础对象的链接.请参阅:https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html#//apple_ref/doc/uid/10000011i同时查看**内存管理策略**(子弹2)和**实用内存管理**,更详细地介绍了集合. (2认同)