繁体   English   中英

深入了解保留循环

[英]Understanding retain cycle in depth

假设我们有三个对象:祖父母、父母和孩子。 祖父母保留父母,父母保留孩子,孩子保留父母。 祖父母释放父母。

在这种情况下会发生什么?

除非有其他对父母或孩子的引用,否则他们都会成为孤儿。 但是父级和子级之间的保留循环可以防止任何一个被释放并且它们成为浪费的内存。

孩子永远不应该留住父母。 如果有的话,请在子级中使用弱引用来维护对父级的引用。

Retain Cycle 是一个条件,当两个对象相互保持引用并被保留时,它会创建一个保留循环,因为两个对象都试图相互保留,从而无法释放。

这里“祖父母”保留“父母”,“父母”保留“孩子”,而“孩子”保留“父母”。在这里,父母和孩子之间建立了一个保留循环。 释放祖父母后,父母和孩子都成为孤儿,但父母的保留计数不会为零,因为它被孩子保留,因此会导致内存管理问题。

有两种可能的解决方案:

1) 使用指向 parent 的弱指针,即一个 child 应该使用对 parent 的弱引用,这不会被保留。

2) 使用“close”方法来中断保留循环。

http://www.cocoawithlove.com/2009/07/rules-to-avoid-retain-cycles.html

在一个简单的例子中,考虑两个对象 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 的人是祖父母。

保留循环是当对象 A 保留对象 B,对象 B 保留对象 A 时发生的循环。在这种情况下,如果释放任一对象:

  • 对象 A 不会被释放,因为对象 B 持有对它的引用(保留计数 > 0)。
  • 只要对象 A 有对它的引用(保留计数 > 0),对象 B 就永远不会被释放。
  • 但是对象 A 永远不会被释放,因为对象 B 持有对它的引用(保留计数 > 0)。
  • 直到无限

因此,这两个对象将在程序的整个生命周期中停留在内存中,即使它们应该(如果一切正常)被释放。

当祖父母释放父母时,父母仍然活着,因为孩子保留父母。

祖父母:约翰父母:泰德孩子:玛丽

这是我使用电话进行说明的示例:

  • 约翰打电话给泰德,想和玛丽开个电话会议。

  • 泰德对约翰说:“挂断电话,我会拨通玛丽”

  • 泰德让约翰保持等待,并给玛丽打电话,玛丽立即接听了电话。

  • 玛丽对泰德说:“把我和约翰的电话合并,我不会挂断电话,直到我完成为止”

  • Ted 有一段时间没有收到 John 的回音,离开电话去做其他事情。

  • 约翰去与泰德和玛丽合并电话,然后突然死了。

  • 玛丽一直挂在约翰的电话线上,但永远不会挂断,因为约翰不会回来!

Retain Cycle两个对象相互保持引用并被保留的条件它创建了一个保留循环,因为两个对象都试图相互保留,从而无法释放。


例子:一个人住在一个​​部门,一个部门有一个人。

@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 日志,这是保留圈。

由于P对象的retainCount为1,释放时其retainCount变为0,调用dealloc方法; 这反过来调用释放 C 对象,其保留计数也变为 0; 并调用它的 dealloc 方法。

对象 P 和 C 都将被释放。

当调用C对象的dealloc方法时,依次调用GP对象的release,但由于GP持有retain count为2,retain count递减为1,继续挂起。

保留循环是一种死锁条件。 Retain Cycle 的真实例子:如果两个对象互相持有一个引用并且没有其他对象被释放。

示例:拉米游戏

当两个对象以这种方式保持彼此的引用时,对象会创建循环并被保留。 两个对象都试图互相保留,在这种情况下它们彼此之间有很强的联系并且无法释放,称为保留循环

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM