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

时间:2012-05-26 18:51:22

标签: objective-c cocoa memory-management

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

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

  

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

所以我读了addObject:个文档:

  

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

在那里,缺少说明,而其他项目,如arrayByAddingObject:说明

  

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

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

3 个答案:

答案 0 :(得分:7)

很棒的问题,我很高兴看到有人在阅读文档并试图了解它们

由于您正在寻找如何使用Apple的文档研究答案,而不是实际的答案本身,以下是我找到答案的方法:

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

特别注意事项

     

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

  • 好的,我还是不开心......下一步是哪里? NSArray的父类是NSObject,我知道在这种情况下(根据经验)它不会被覆盖,所以我不打算检查它。 (如果父母是另一个类或可能被NSObject覆盖的东西,我会继续向上移动树,直到我找到了什么。)
  • Companion Guides 通常包含许多针对这些类型的良好信息。让我们试试第一个,收藏编程主题
  • 第一部分(在概述之后)访问索引并轻松枚举元素:数组。听起来很有希望!点击相关章节:“数组:有序集合”
  • 它位于数组基础下,并提供了更多信息的链接:
  

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

答案 1 :(得分: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,它将在其自动释放池被耗尽时释放(可能在该线程的下一次迭代中#39; s 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访问器(setSomeOtherArray:),或者您仍然可以使用{{1}直接设置iVar(假设你someOtherArray = [someArray arrayByAddingObject:someObject];)。

或者假设非ARC,您可能已声明@synthesized这样的属性,其中@property (nonatomic, retain) NSArray *someOtherArray的行为与ARC完全相同,但在直接设置iVar时,您仍需要手动添加该保留

我希望稍微澄清一下,如果有任何我掩盖或遗漏的事情,请告诉我。


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

  • 当设置框架对象的self.someOtherArray = [someArray arrayByAddingObject:someObject];属性(例如NSString的{​​{1}}属性)时,它总是被复制(如果有人知道反例,请评论或编辑)。一旦你通过它,你不必担心你的字符串。复制字符串是为了防止可变字符串在传递后被更改。
  • 当设置text以外的任何其他属性时,它(几乎?)始终保留(或ARC中的强引用)
  • 设置委托属性时,它(几乎?)总是指定(或弱引用)以防止循环保留周期。 (例如,对象UILabel具有强引用的属性delegatea具有强引用的委托属性。您将b设置为{{1}的委托现在ba都强烈引用彼此,并且任何对象都不会达到保留计数0并且永远不会达到它的dealloc方法来释放另一个对象。{ {1}}是一个反例,它强烈引用它的委托,因为它的委托是通过一种方法设置的 - 参见下面的惯例 - 并且它的惯例是nil out或者在b完成后发布a而不是b,这将取消通函保留)
  • 添加到数组或字典时,它始终保留(或强引用)。
  • 当调用方法并传递块时,它们总是被复制以将它们从堆栈(最初为了性能目的而创建它们)移动到堆中。
  • 接收对象参数并且不立即返回结果的方法(总是?我不能想到任何不要复制或保留(强引用)参数的方法您传递以确保该方法可以执行它们所需的操作。例如,NSURLConnection甚至保留了它的委托,因为它是通过方法传入的,而当设置其他对象的委托属性时将不会保留,因为是惯例。

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

另外,请不要忘记所有课程的标题都可供您使用,这样您就可以轻松查看属性是保留还是分配(或强或弱)。你不能检查哪些方法对它们的参数做了什么,但是由于接收器拥有参数的惯例,所以没有必要。

答案 2 :(得分:5)

一般而言,您应该查看“最全球”的位置,以获取有关Cocoa API中任何内容的信息。由于内存管理遍布系统API ,因此API在Cocoa内存管理策略的实现中是一致的,您只需阅读并理解Cocoa内存管理指南。

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

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

您将在整个文档中看到这一点。这可以防止每个方法的文档都是一个页面或更长的当罕见的方法或类实现的东西,无论出于何种原因(有时不太好),规则的异常


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

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

  

通常保证收到的对象在其中保持有效   它收到的方法,并且该方法也可以安全地返回   反对它的调用者。你在两种情况下使用retain:(1)在   执行accessor方法或init方法   要存储为属性值的对象的所有权; (2)   防止某个对象因某些副作用而失效   其他操作(如“避免导致重新分配”中所述)   你正在使用的对象“)。

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