本文共 5232 字,大约阅读时间需要 17 分钟。
虽然中描述的基本概念很简单,但您可以采取一些实际步骤来简化内存管理,并帮助确保您的程序保持可靠和健壮,同时最大限度地减少其资源需求。
如果您的类具有作为对象的属性,则必须确保在您使用它时不会释放任何设置为该值的对象。因此,您必须在设置对象时声明对象的所有权。您还必须确保放弃任何当前持有的价值的所有权。
有时它可能看起来很乏味或迂腐,但如果你一直使用访问器方法,那么内存管理问题的可能性会大大降低。如果您在整个代码中使用retain
和release
实例变量,那么您几乎肯定会做错事。
考虑一个您想要设置其计数的 Counter 对象。
@interface Counter:NSObject |
@property(非原子,保留)NSNumber *计数; |
@结束; |
该声明了两种访问方法。通常,您应该要求编译器合成方法;但是,了解它们如何实施是有益的。
在 “get” 访问器中,您只需返回合成的实例变量,因此不需要retain
或release
:
- (NSNumber *)count { |
return _count; |
} |
在 “设置” 方法中,如果其他所有人都按照相同的规则进行游戏,则必须假设新计数可以随时处理,因此您必须取得对象的所有权 - 通过向其发送retain
消息 - 以确保赢得不会。您还必须通过向其发送release
消息来放弃旧计数对象的所有权。(nil
在 Objective-C 中允许发送消息,因此如果_count
尚未设置,实现仍然有效。)如果[newCount retain]
两个是同一个对象,你必须发送它 - 你不想无意中导致它要解除分配。
- (void)setCount:(NSNumber *)newCount { |
[newCount保留]; |
[_count release]; |
//进行新的作业。 |
_count = newCount; |
} |
假设您要实现重置计数器的方法。你有几个选择。第一个实现创建NSNumber
实例alloc
,所以你用 a 平衡它release
。
- (void)reset { |
NSNumber * zero = [[NSNumber alloc] initWithInteger:0]; |
[self setCount:zero]; |
[零释放]; |
} |
第二个使用便利构造函数来创建新NSNumber
对象。因此不需要retain
或release
消息
- (void)reset { |
NSNumber * zero = [NSNumber numberWithInteger:0]; |
[self setCount:zero]; |
} |
请注意,两者都使用 set 访问器方法。
对于简单的情况,以下几乎肯定会正常工作,但是尽可能避免使用访问器方法,这样做几乎肯定会在某个阶段导致错误(例如,当您忘记保留或释放时,或者如果实例变量的内存管理语义更改)。
- (void)reset { |
NSNumber * zero = [[NSNumber alloc] initWithInteger:0]; |
[_count release]; |
_count =零; |
} |
另请注意,如果使用,则以这种方式更改变量不符合 KVO。
您不应该使用访问器方法来设置实例变量的唯一地方是方法和dealloc
。要使用表示零的数字对象初始化计数器对象,您可以实现init
如下方法:
- 在里面 { |
self = [super init]; |
if(self){ |
_count = [[NSNumber alloc] initWithInteger:0]; |
} |
回归自我; |
} |
要允许使用非零计数初始化计数器,您可以实现initWithCount:
如下方法:
- initWithCount:(NSNumber *)startingCount { |
self = [super init]; |
if(self){ |
_count = [startingCount copy]; |
} |
回归自我; |
} |
由于 Counter 类具有对象实例变量,因此还必须实现dealloc
方法。它应该通过向它们发送release
消息来放弃任何实例变量的所有权,并最终应该调用 super 的实现:
- (void)dealloc { |
[_count release]; |
[super dealloc]; |
} |
保留对象会创建对该对象的强引用。在释放所有强引用之前,不能释放对象。因此,如果两个对象可能具有循环引用,则会出现一个被称为保留周期的问题 - 也就是说,它们彼此之间具有强烈的引用(直接或通过一系列其他对象,每个对象都强烈引用下一个对象回到第一个)。
所示的对象关系说明了潜在的保留周期。Document 对象具有文档中每个页面的 Page 对象。每个 Page 对象都有一个属性,用于跟踪它所在的文档。如果 Document 对象具有对 Page 对象的强引用,并且 Page 对象具有对 Document 对象的强引用,则任何对象都不能被释放。在释放 Page 对象之前,Document 的引用计数不能为零,并且在取消分配 Document 对象之前不会释放 Page 对象。
图 1 周期性参考的图示
保留周期问题的解决方案是使用弱引用。甲弱引用是一个非所属关系,其中源对象不保留它具有参考的对象。
但是,为了保持对象图完好无损,必须在某处有强引用(如果只有弱引用,则页面和段落可能没有任何所有者,因此将被释放)。因此,可可建立了一个惯例,即 “父母” 对象应该对其 “孩子” 保持强烈的引用,并且孩子们应该对父母的弱引用。
因此,在,文档对象具有对(保留)其页面对象的强引用,但页面对象具有对(不保留)文档对象的弱引用。
Cocoa 中弱引用的示例包括但不限于表数据源,大纲视图项,观察器以及其他目标和。
您需要注意将消息发送到仅包含弱引用的对象。如果在取消分配对象后向对象发送消息,则应用程序将崩溃。对象有效时,您必须具有明确定义的条件。在大多数情况下,弱引用对象知道另一个对象对它的弱引用,就像循环引用的情况一样,并且负责在解除分配时通知另一个对象。例如,当您向通知中心注册对象时,通知中心会存储对该对象的弱引用,并在发布相应的通知时向其发送消息。取消分配对象后,需要将其注销到通知中心,以防止通知中心向对象发送任何进一步的消息,不再存在。同样,当取消分配委托对象时,您需要通过发送一个来删除委托链接setDelegate:
带有nil
另一个对象的参数的消息。这些消息通常是从对象的dealloc
方法发送的。
Cocoa 的所有权策略指定接收的对象通常应该在调用方法的整个范围内保持有效。还应该可以从当前范围返回接收到的对象而不用担心它被释放。对于您的应用程序而言,对象的 getter 方法返回缓存的实例变量或计算值应该无关紧要。重要的是该对象在您需要的时间内仍然有效。
此规则偶尔会有例外情况,主要分为两类。
从一个基本删除对象时。
heisenObject = [array objectAtIndex:n]; |
[array removeObjectAtIndex:n]; |
// heisenObject现在可能无效。 |
当从一个基本集合类中删除一个对象时,会发送一个release
(而不是autorelease
)消息。如果集合是已删除对象的唯一所有者,heisenObject
则会立即释放已删除的对象(在示例中)。
当 “父对象” 被释放时。
id parent = <#create父对象#>; |
// ... |
heisenObject = [父子]; |
[家长发布]; //或者,例如:self.parent = nil; |
// heisenObject现在可能无效。 |
在某些情况下,您从另一个对象检索对象,然后直接或间接释放父对象。如果释放父节点导致它被释放,并且父节点是子节点的唯一所有者,那么子节点(heisenObject
在示例中)将同时被释放(假设在父节点中发送它release
而不是autorelease
消息)dealloc
方法)。
为了防止这些情况发生,您heisenObject
在收到它后会保留,并在完成后将其释放。例如:
heisenObject = [[array objectAtIndex:n] retain]; |
[array removeObjectAtIndex:n]; |
//使用heisenObject ... |
[heisenObject发布]; |
您通常不应该管理方法中的稀缺资源,例如文件描述符,网络连接以及缓冲区或缓存dealloc
。特别是,您不应该设计类,以便dealloc
在您认为将调用它时调用它们。dealloc
由于错误或应用程序拆除,调用可能会被延迟或回避。
相反,如果你有一个实例管理稀缺资源的类,你应该设计你的应用程序,以便你知道何时不再需要资源,然后可以告诉实例在那时 “清理”。您通常会释放该实例,然后dealloc
关注,但如果不这样做,您将不会遇到其他问题。
如果你试图在上面捎带资源管理,可能会出现问题dealloc
。例如:
拆除的顺序依赖性。
对象图拆除机制本质上是无序的。虽然您通常会期望并获得特定订单,但您会引入脆弱性。如果对象意外地自动释放而不是例如释放,则拆卸顺序可能会改变,这可能会导致意外结果。
不回收稀缺资源。
内存泄漏是应该修复的错误,但它们通常不会立即致命。但是,如果在您希望释放资源时没有释放稀缺资源,则可能会遇到更严重的问题。例如,如果您的应用程序用完了文件描述符,则用户可能无法保存数据。
在错误的线程上执行清理逻辑。
如果一个对象在意外的时间自动释放,它将在它碰巧进入的任何线程的自动释放池块中被释放。对于只能从一个线程触及的资源来说,这很容易致命。
将对象添加到(例如数组,字典或集合)时,集合将获得对象的所有权。当从集合中移除对象或集合本身被释放时,集合将放弃所有权。因此,例如,如果要创建数字数组,可以执行以下任一操作:
NSMutableArray * array = <#Get a mutable array#>; |
NSUInteger i; |
// ... |
for(i = 0; i <10; i ++){ |
NSNumber * convenienceNumber = [NSNumber numberWithInteger:i]; |
[array addObject:convenienceNumber]; |
} |
在这种情况下,您没有调用alloc
,因此无需调用release
。没有必要保留新的数字(convenienceNumber
),因为数组会这样做。
NSMutableArray * array = <#Get a mutable array#>; |
NSUInteger i; |
// ... |
for(i = 0; i <10; i ++){ |
NSNumber * allocedNumber = [[NSNumber alloc] initWithInteger:i]; |
[array addObject:allocedNumber]; |
[allocedNumber release]; |
} |
在这种情况下,你就需要发送allocedNumber
一个release
该范围内的消息for
循环平衡alloc
。由于数组在添加时保留了数字,因此在数组中addObject:
它不会被释放。
要理解这一点,请将自己置于实现集合类的人的位置。你想要确保没有给你照看的对象从你身下消失,所以retain
当你传入它们时你会发送一条消息。如果它们被删除,你必须发送一个平衡release
消息,任何应该release
在您自己的dealloc
方法中向剩余对象发送消息。
所有权政策通过引用计数实现 - 通常在方法之后称为 “保留计数” 。每个对象都有一个保留计数。
创建对象时,其保留计数为 1。
向对象发送retain
消息时,其保留计数增加 1。
向对象发送消息时,其保留计数减 1。
向对象发送消息时,其保留计数在当前自动释放池块的末尾减 1。
如果对象的保留计数减少到零,则将其取消分配。
重要提示: 没有理由明确询问对象的保留计数是什么(请参阅参考资料)。结果通常会产生误导,因为您可能不知道哪些框架对象保留了您感兴趣的对象。在调试内存管理问题时,您应该只关心确保代码遵守所有权规则。
转载地址:http://mqhwn.baihongyu.com/