A quick explanation of retain cycles requires that I quickly summarize how hierarchies of objects are tied together. Specifically: objects in a hierarchy are created, owned and freed in a chain along the hierarchy.
In this example, "Some Object" owns Parent which in turn owns the Child.
When the hierarchy is no longer needed, "Some Object" sends a
release
to the Parent. The Parent's retainCount
hits zero, causing its dealloc
method to run. In its dealloc
method, it sends a release
to the Child, successfully freeing the chain. This is the correct behavior.The problem of retain cycles occurs when the Child needs a pointer to the Parent for any reason and it chooses to retain the Parent. This alters the diagram to the following:
In this diagram, when "Some Object" sends a
release
to the Parent, the retainCount
does not reach zero (since the Child's retain of the Parent has incremented the retainCount
for itself). Since the Parent's retainCount
does not reach zero, its dealloc
method never gets called and so it never sends a release
to the Child.This is a retain cycle: with the Parent retaining the Child and the Child retaining the Parent, they continue to exist, cut off from the rest of the objects in the program but keeping themselves alive. They have leaked.
Avoiding retain cycles
An object must never retain its parent
The first rule to avoid retain cycles is that an object must never retain its parent. This changes the previous diagram to the following:This is the easy case: an object should never retain its parent when it makes a pointer to the parent.
Notice that the term "weak pointer" is used. A weak pointer is one that does not retain its target.
Of course, now that the child doesn't retain the parent, the child must be aware of any situation where the parent becomes invalid (freed) and not use its pointer to the parent in that case.
So, either:
- The parent must set the
parent
pointer in the child tonil
when the relationship is broken.
or - The design must guarantee that the parent pointer is always valid for the child's lifetime (or if the parent uses
autorelease
to free children, valid except in the child'sdealloc
method).
nil
or the design must otherwise prevent invalid use.Generally speaking, the option of setting to
nil
is much
safer. The only downside is that it requires a way of detecting the
pending release of the target and nominating an object whose role it is
to make that detection and update the pointer accordingly. It is easy
here where the parent (the target of the weak pointer) knows about the
child and is also the hierarchical manager of the child but not all
retain cycles are one-to-one like this.Four common errors to look out for that may cause your retain count to be higher than expected.
1. NSTimer
If you create an
NSTimer
object on a view controller, be sure that invalidate
is called on it when dismissing the view controller, otherwise it will retain self
.2. Observers/NSNotificationCenter
If you add an observer to
NSNotificationCenter
, make sure you remove all observers when dismissing the view controller, otherwise they will retain self.3. Blocks
You should not call
[self doSomething]
from inside a block, this can easily lead to the block capturing a reference to self
. Instead, make a weak reference to self
:BAD:
GOOD:
dispatch_async(queue, ^{ [self doSomething]; });
4. Delegates
__weak MyViewController *safeSelf = self; dispatch_async(queue, ^{ [safeSelf doSomething]; });
if you use
inside the view controller, check the delegate property on someObj is weak.
someObj.delegate = self;
Once you've made your fixes, check that dealloc is getting hit and the allocations no longer increase endlessly.
@property (nonatomic, weak) id delegate;
No comments:
Post a Comment