Friday 6 November 2015

Retain Cycle




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.
retaincycle1.png
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:

retaincycle2.png


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:

retaincycle3.png

 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 to nil 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's dealloc method).
These two options apply to weak pointers in general: once the target becomes invalid, the pointer must be set to 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:
  1. dispatch_async(queue, ^{
  2. [self doSomething];
  3. });
GOOD:
  1. __weak MyViewController *safeSelf = self;
  2. dispatch_async(queue, ^{
  3. [safeSelf doSomething];
  4. });
4. Delegates
if you use
  1. someObj.delegate = self;
inside the view controller, check the delegate property on someObj is weak.
  1. @property (nonatomic, weak) id delegate;
Once you've made your fixes, check that dealloc is getting hit and the allocations no longer increase endlessly.

 

No comments:

Post a Comment