3

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

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

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

所以我阅读了addObject:文档:

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

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

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

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

4

3 回答 3

7
于 2012-05-26T20:13:40.997 回答
6

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

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

“ARC为此带来了什么?”

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


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


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

当然,现在你必须凭直觉知道什么是所有权。例如,委托属性通常assign是 ARCunsafe_unretained或ARC 中的weak,以防止循环保留循环(两个对象彼此保留),尽管有时保留/强,因此您需要逐个研究这些。

并且在像键值观察和 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 *someOtherArrayinself.someOtherArray = [someArray arrayByAddingObject:someObject];那样会达到相同的效果,但会使用属性访问器 ( setSomeOtherArray:),或者你仍然可以使用someOtherArray = [someArray arrayByAddingObject:someObject];直接设置 iVar(假设你@synthesized这样做)。

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

我希望能把事情弄清楚一点,如果有什么我掩盖或遗漏的地方,请告诉我。


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

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

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

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

于 2012-05-26T18:59:29.970 回答
5

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

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

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

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


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

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

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

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

于 2012-05-26T22:08:49.077 回答