深入了解保留周期
Posted
技术标签:
【中文标题】深入了解保留周期【英文标题】:Understanding retain cycle in depth 【发布时间】:2013-11-22 10:07:37 【问题描述】:假设我们有三个对象:祖父母、父母和孩子。祖父母留住父母,父母留住孩子,孩子留住父母。祖父母释放父母。
这种情况会发生什么?
【问题讨论】:
【参考方案1】:除非有其他对父母或孩子的引用,否则他们都会成为孤儿。但是父子之间的保留循环阻止了它们被释放并且它们成为浪费的内存。
孩子永远不应该保留父母。如果有的话,请在子级中使用弱引用来维护对父级的引用。
【讨论】:
只是想补充一点,在极少数情况下,让孩子保留父母可能会很有用,您只需要添加一个在某个时候打破循环的方法(并保证它会在需要清理对象时调用)。 @Taum 孩子什么时候需要保留父母?对父母有弱引用很好,但为什么要强引用? @rmaddy 可能是您将动画或其他基于计时器的活动设置为关闭自行运行,而您不再拥有它。您会希望它在需要时一直存在,并自行清理。【参考方案2】:保留周期是条件当两个对象保持对彼此的引用并被保留时,它会创建一个保留周期,因为两个对象都试图相互保留,因此无法释放。
这里“祖父母”保留“父母”,“父母”保留“孩子”,而“孩子”保留“父母”。这里在父母和孩子之间建立了一个保留循环。释放祖父母后,父母和孩子都成为孤儿,但父母的保留计数不会为零,因为它被孩子保留,因此会导致内存管理问题。
有两种可能的解决方案:
1) 使用指向 parent 的弱指针,即孩子应该使用对 parent 的弱引用,它不会被保留。
2) 使用“关闭”方法来中断保留周期。
http://www.cocoawithlove.com/2009/07/rules-to-avoid-retain-cycles.html
【讨论】:
【参考方案3】:在一个简单的情况下,考虑两个对象 A 和 B,其中 A 创建并保留 B。创建 A 时,它会创建 B。当创建 A 的人最终释放它时,A 的保留计数下降到零并被释放。如果 A 的 dealloc 方法在 B 上调用 release,则 B 的保留计数也会下降到零,并且它也会被释放。 [这里假设没有其他人保留 A 或 B,因为我保持简单。]
但是如果 B 需要返回 A 的引用,并且它保留了 A,会发生什么?创建 A 的人可能会释放它。但是由于 B 也保留了 A,所以 A 的保留计数不会变为零。同样,由于 A 保留 B,B 的保留计数也不会归零。两者都不会被释放。即使 B 在其自己的 dealloc 中调用 A 的 release 方法也没关系,因为该方法永远不会被调用。
此时您有内存泄漏,因为您没有对 A 或 B 的任何引用,即使它们仍然存在。如果 A 或 B 正在做任何处理器密集型的事情,那么您也可能会将 CPU 时间泄漏给不需要的对象。
在您的情况下,A 是父母,B 是孩子,而创建 A 的人是祖父母。
【讨论】:
【参考方案4】:保留循环是当对象 A 保留对象 B,而对象 B 保留对象 A 时发生的循环。在这种情况下,如果任一对象被释放:
对象 A 不会被释放,因为对象 B 持有对 它(保留计数 > 0)。 只要对象 A 有一个 引用它(保留计数 > 0)。 但是对象 A 永远不会被释放,因为对象 B 拥有一个 引用它(保留计数 > 0)。 直到无穷因此,这两个对象将在程序的整个生命周期中一直在内存中徘徊,即使如果一切正常,它们应该被释放。
【讨论】:
@Balasubramanian 我们在任一对象中保持弱引用,取决于哪个对象拥有另一个对象,所有者应该保持强引用和另一个弱引用。【参考方案5】:当祖父母释放父母时,父母仍然活着,而孩子保留父母。
【讨论】:
【参考方案6】:祖父母:约翰 家长:泰德 孩子:玛丽
这是我使用电话进行说明的示例:
John 给 Ted 打电话,想和 Mary 进行电话会议。
Ted 对 John 说:“挂断电话,我会拨入 Mary”
Ted 让 John 处于等待状态,然后打电话给 Mary,Mary 立即接听了电话。
Mary 对 Ted 说:“将我的电话与 John 合并,我不会挂断,直到我结束”
Ted 有一段时间没有收到 John 的回音,于是离开电话去做其他事情。
John 去合并 Ted 和 Mary 的电话,然后突然死了。
玛丽挂在约翰的电话上,但永远不会挂断,因为约翰不会回来!
【讨论】:
【参考方案7】:保留周期是两个对象保持对彼此的引用并被保留的条件,它创建一个保留周期,因为两个对象试图挽留对方,使其无法释放。
例子:一个人住在一个部门,一个部门有一个人。
@class Department;
@interface Person:NSObject
@property (strong,nonatomic)Department * department;
@end
@implementation Person
-(void)dealloc
NSLog(@"dealloc person");
@end
@interface Department: NSObject
@property (strong,nonatomic)Person * person;
@end
@implementation Department
-(void)dealloc
NSLog(@"dealloc Department");
@end
然后这样称呼它:
- (void)viewDidLoad
[super viewDidLoad];
Person * person = [[Person alloc] init];
Department * department = [[Department alloc] init];
person.department = department;
department.person = person;
你不会看到dealloc日志,这是retain circle。
【讨论】:
【参考方案8】:由于P对象的retainCount为1,释放时,其retainCount变为0,调用其dealloc方法;这反过来又调用 C 对象的释放,其保留计数也变为 0;并调用它的dealloc方法。
对象 P 和 C 都将被释放。
当调用 C 对象的 dealloc 方法时,又调用了 GP 对象的释放,但由于 GP 持有一个保留计数为 2,保留计数减为 1,并继续挂起。
【讨论】:
P 在被 GP 释放之前的 retainCount 为 2(它被 GP 和 C 保留)。 你是对的。阅读原始问题略有不同且不正确-:)。我读到的问题是 gp->p->c->gp 保留循环而不是 gp->p->c->p 循环。【参考方案9】:保持循环是一个死锁条件。 保留周期的真实示例: 如果两个对象相互持有引用,并且没有其他对象被释放。
示例:拉米游戏
【讨论】:
【参考方案10】:当两个对象以这种方式保持相互引用时,对象创建循环并被保留。两个对象都试图互相保留,在这种情况下,它们之间是强连接的,无法释放的称为保留循环
【讨论】:
以上是关于深入了解保留周期的主要内容,如果未能解决你的问题,请参考以下文章