Debugging retain cycles in Objective-C: four likely culprits

Your iPhone app seems to be working fine. But suddenly, it starts to run slow and crashes! You suspect it's memory-related. Here's how to track down and fix issues with retain cycles, which can cause memory not to be released properly.

I'm assuming you are using ARC and iOS5.0+. While ARC simplifies a lot of memory management, it won't spot all retain cycles for you!

First, check you're genuinely dealing with a memory condition. Try to reproduce the crash on a device, and look for crash reports using Xcode > Organiser > Device Logs.

Low memory crashes don't look like normal crash reports, you won't see a stack trace! Instead you'll probably just see Process = Unknown, and the crash report will contain a list of processes which were running at the time of the crash.

Screen Shot 2013-04-16 at 14.01.59

In this example, you can see that Biblegram was using over 25000 pages of memory. 1 page of memory is 4KB, so that's 100MB of memory, which seems much too high.

Next, fire up Instruments via Product > Profile and select the "Allocations" template. Playing around with the app, you should easily be able to locate places where the memory allocation keeps going up and up.

Screen Shot 2013-04-16 at 14.12.13

This "staircase" pattern is a giveaway. I'm repeatedly pushing and popping one view controller, but the memory goes up and up.

Try putting a breakpoint in the dealloc method of the problematic view controller. Most likely, this will never get hit, showing that the view controller is never released.

Screen Shot 2013-04-16 at 14.30.22

This is a strong indication that we have a retain cycle. When the view controller is dismissed there are still some strong references to it, so it doesn't get dealloced.

As one last verification, enter the name of the class into the search box in the top right of Instruments, and check the "# living" column. This is showing 3, when we'd expect it to show 1.

Screen Shot 2013-04-16 at 14.15.38

You can drill down into the instances of the view controller by tapping the small arrow to the right of the class name, and drill down further to see every place that the view controller is retained and released.

This view can be rather overwhelming: the system frameworks do a lot of retaining and releasing on your behalf! For example, instantiating a NIB can easily increase the retain count to 40 temporarily.

So: here are 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.  
  2. dispatch_async(queue, ^{
  3. [self doSomething];
  4. });
  5.  

GOOD

  1.  
  2. __weak MyViewController *safeSelf = self;
  3. dispatch_async(queue, ^{
  4. [safeSelf doSomething];
  5. });
  6.  

4. Delegates

if you use

  1.  
  2. someObj.delegate = self;
  3.  

inside the view controller, check the delegate property on someObj is weak.

  1.  
  2. @property (nonatomic, weak) id delegate;
  3.  

Once you've made your fixes, check that dealloc is getting hit and the allocations no longer increase endlessly.

Matt Mayer

Matt Mayer is a founder at ReignDesign. Matt is from the UK and was based in Shanghai for ten years. He is now living in Bangkok, Thailand.

17 comments

  1. Hi, I don’t think the first example you give really does create a retain cycle. Since the block is not being retained by self, once the block has completed self will be released and then the block.

  2. Actually the first example can indeed cause a retain cycle. This post just reminded me to look for that in a VC that was stubbornly hanging around after being nilled by its owner. A non weak reference inside a 1 line block was the culprit. Thanks for the refresher!

  3. Great post with one big mistake:
    NSNotificationCenter doesn’t retain observers but it creates a dangling pointer issue if you forget to remove observer.

  4. In the block’s example
    Suppose I have invoked the doSomething method as
    [safeSelf doSomething];

    But if in doSomething, it is referencing some instance member with self property will it create the retain cycle problem?

    -(void)doSomething
    {
    self.someProperty = [self someMethod];

    }

  5. Actually, the first example never leads to a retain cycle, because self is not strongly referencing the block. It may also be dangerous to recommend always using weak references, because the inner self could become nil given some circumstances. Maybe you should correct your post.

  6. Awesome post ! Thank you so much, Matt.
    dealloc of my ViewController was not getting called and the culprit is notification observer, I have removed it and I got it working.

Leave a Reply

Your email address will not be published. Required fields are marked *