简体   繁体   English

保留周期:为什么这么糟糕?

[英]Retain Cycles: Why is that such a bad thing?

There are two Objects A and B. A creates B and retains it. 有两个对象A和B. A创建B并保留它。 B has an instance variable that points to A, retaining it. B有一个指向A的实例变量,保留它。 So both retain eachother. 所以两者都保持着彼此。 Some people say, that this strong connection can't be broken ever again. 有人说,这种强大的联系不能再被打破。

But is that really the case? 但情况确实如此吗?

If B would release A, then A could easily release B, and so B would be deallocated. 如果B将释放A,则A可以轻松释放B,因此B将被释放。 A would be deallocated as soon as it's other owner (I guess there must be someone) releases it. 一旦它的其他所有者(我想必须有人)释放它,它就会被解除分配。

Or does this problem only apply in a case where A does not create B, but just holds a strong reference to it through retaining it in an instance variable? 或者此问题仅适用于A不创建B的情况,但只是通过将其保留在实例变量中来保存对它的强引用? I still don't see why that connection could not be broken up again. 我仍然不明白为什么这种联系不能再被打破。

Cycles aren't bad, but they are often avoided because they can make it tricky to ensure you haven't got memory leaks. 循环并不坏,但它们经常被避免,因为它们可以使它变得棘手,以确保您没有内存泄漏。 Leaks occur especially when objects are 'reference counted'. 特别是当对象被“引用计数”时会发生泄漏。 In a language or system that uses reference counting, an object keeps track of the number of references pointing at it. 在使用引用计数的语言或系统中,对象会跟踪指向它的引用数。 Every time a reference is deleted, the count goes down, when the count gets to zero, there are no references and so the object can be deleted. 每次删除引用时,计数都会下降,当计数变为零时,没有引用,因此可以删除该对象。

This usually takes care of itself and works ok without any careful thinking. 这通常会照顾好自己,并且无需仔细考虑即可正常工作。 If you've got a group of objects with no cycles and you drop your reference to the root object, then it will be deleted, this means references it has to objects it owns will be dropped, the objects being referenced will have their reference counts go to zero. 如果你有一组没有循环的对象,你删除了对根对象的引用,那么它将被删除,这意味着它对它拥有的对象的引用将被删除,被引用的对象将具有它们的引用计数归零。 They'll be deleted and the cascade will cause all objects to be deleted. 它们将被删除,级联将导致所有对象被删除。

But... if you have a cycle, this cascade doesn't work. 但是......如果你有一个循环,这个级联不起作用。 You may have a group of objects and you don't want them any more, so you drop the only reference you have to these objects, but because there is a cycle the objects reference each other. 您可能有一组对象,并且您不再需要它们,因此您只删除对这些对象的唯一引用,但因为有一个循环,所以对象相互引用。 This means their reference counts never go to zero, and they don't get deleted. 这意味着它们的引用计数永远不会为零,并且它们不会被删除。 This is a memory leak. 这是内存泄漏。

Clearly, you can do some careful management and break the cycles before you drop your reference to a group of objects you don't want any more. 显然,在删除对一组不再需要的对象的引用之前,您可以进行一些仔细的管理并打破周期。 But... as I just said, this takes careful management. 但是......正如我刚才所说,这需要谨慎管理。 It's very easy to get wrong. 这很容易出错。 This is one of the main reasons that memory leaks occur. 这是发生内存泄漏的主要原因之一。

To avoid the risk of leaks and the tricky job of breaking cycles correctly when you no longer need a group of objects, programmers usually try to avoid cycles. 当您不再需要一组对象时,为了避免泄漏风险和正确打破周期的棘手工作,程序员通常会尝试避免循环。 This becomes more important on big projects with many programmers where no one person understands the whole system. 对于那些没有人理解整个系统的程序员而言,这对于大型项目来说变得更加重要。 If there were cycles, the programmers would have to watch out and spend a long time studying each others code to avoid cycles. 如果存在周期,程序员必须注意并花费很长时间来研究彼此的代码以避免循环。

Some languages with garbage collectors (eg C#) can delete a group of objects that are no longer needed even if the group contains cycles. 某些带有垃圾收集器的语言(例如C#)可以删除一组不再需要的对象,即使该组包含循环也是如此。

A retain cycle can be broken, if you know about it. 如果您了解保留周期,可以打破保留周期。 Usually it leads to nasty bugs (memory leaks). 通常它会导致讨厌的错误(内存泄漏)。 In your example: 在你的例子中:

A* a = [[A alloc] initAndCreateB];

Now, a unnamed B instance (created by A) has a retain count of 1. Since we hold a reference to A and the anonymous B instance holds a strong reference to A, A's retain count is 2. 现在,一个未命名的B实例(由A创建)的保留计数为1.由于我们持有对A的引用而匿名B实例持有对A的强引用,因此A的保留计数为2。

Let's say, we are done using A: 比方说,我们完成了使用A:

[a release];
return 12;

Now, A's retain count is 1. It will not be released, it's memory is lost. 现在,A的保留计数为1.它不会被释放,它的内存会丢失。 That's why retain cycles are bad. 这就是保留周期不好的原因。

The way to break a retain loop is to have a separate "close" method. 打破保留循环的方法是使用单独的“关闭”方法。

ie

A retains B
B retains A

when you're done, call a method (I'll call it " close ") on A where A releases B. You can then release A and the whole loop will release (assuming there are no retains elsewhere). 当你完成后,在A上调用一个方法(我将其称之为“ close ”),其中A释放B.然后你可以释放A并且整个循环将释放(假设其他地方没有保留)。

The problem is this: A points to and retains B, and B points to and retains A. When there are no other references to A or B, there will be no way to release them, because you app doesn't have any references to them at that point. 问题是:A指向并保留B,B指向并保留A.当没有其他对A或B的引用时,将无法释放它们,因为您的应用程序没有任何引用他们就在那一刻。 This is called a reference cycle, and it a type of memory leak common in any reference counted system. 这称为参考周期,它是任何参考计数系统中常见的一种内存泄漏。 The way this is solved in most high level languages is by using garbage collection rather than reference counting. 在大多数高级语言中解决这个问题的方法是使用垃圾收集而不是引用计数。

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

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