WIJL: IOS Memory Management Part 2

A few months ago, I posted an entry about some lessons learned in IOS Memory Management. In that entry (WIJL: IOS Memory Management), I covered a bunch of little objective-c memory management gotchas that helped us squash a few major memory leak bugs in TumbleOn.

In the past few months, we’ve done some more work on TumbleOn, and we’ve learned a few more tricks of the trade, including some not-so-obvious problems, and some obvious bits that are worthwhile additions to any memory-leak-hunter’s checklist.

First, the not so obvious bits:

Not So Obvious Memory Problem #1: UIWebView Antics

It appears UIWebView is a bit quirky when it comes to memory management. Some theories maintain that the UIWebView will release properly if you load empty content into the UIWebView before deallocation, while other theories amount to voodoo magic tricks that seem to work for us.

I’ve rolled up an easy-to use objective-c category class which provides a simple cleanBeforeDealloc extension to UIWebView. Pay special attention to the class’ many comments, as none of the tricks encapsulated in the class are my own, and some tricks are merely documented there and will require additional work in your UIWebViewDelegate class and/or UIWebView allocation methods.

Read more, and download the category class here: Code – UIWebView Memory Leak Prevention.

Not So Obvious Memory Problem #2: Careful with those returns

This one keeps biting us over and over again:

  1.   (void) someMethod
  2.  {
  3.    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  
  4.    NSObject * autoReleaseObject = [[[NSObject alloc] init] autorelease];
  5.    NSObject * nonAutoReleaseObject = [[NSObject alloc] init];
  6.    
  7.    if (/* something bad happened */)
  8.    {
  9.       //bug: should release non auto release objs here, and drain our pool      
  10.       return;
  11.    }
  12.    
  13.    /* do stuff with objects */
  14.    
  15.    [nonAutoReleaseObject release];
  16.    [pool drain];
  17.   }

We keep leaking objects by not releasing them when we return (line 10 above) for some error case. Make sure you drain your pools and release objects before return, like this:

  1.   (void) someMethod
  2.  {
  3.    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  
  4.    NSObject * autoReleaseObject = [[[NSObject alloc] init] autorelease];
  5.    NSObject * nonAutoReleaseObject = [[NSObject alloc] init];
  6.    
  7.    if (/* something bad happened */)
  8.    {
  9.       [nonAutoReleaseObject release];
  10.       [pool drain];    
  11.       return;
  12.    }
  13.    
  14.    /* do stuff with objects */
  15.    
  16.    [nonAutoReleaseObject release];
  17.    [pool drain];
  18.   }

Not So Obvious Memory Problem #3: Circular References

This one is fairly simple. Consider the following scenario, where we have a Parent and Child object, each pointing to each other with (retain) properties:

  1.  
  2. //a parent with a child property
  3. @interface Parent : NSObject
  4. {
  5.    NSObject * child;
  6. }
  7.  
  8. @property (retain) NSObject * child;
  9.  
  10. @end
  11.  
  12. //a child with a parent property
  13. @interface Child : NSObject
  14. {
  15.    NSObject * parent;
  16. }
  17.  
  18. @property (retain) NSObject * parent;
  19.  
  20. @end
  21.  
  22. (void) someMethod
  23. {
  24.   self.parent = [[[Parent alloc] init] autorelease]; //parent reference count 2 (init, and 'self' retain property assignment..)
  25.   Child * child = [[[Child alloc] init] autorelease]; //child reference count 1
  26.   parent.child = child; //child reference count 2
  27.   child.parent = parent; //parent reference count 3
  28.  
  29.   self.parent = nil; //parent reference count 2
  30.   //bug: circular reference prevents parent & child from deallocating
  31. }

In this scenerio, the reference count at the end of someMethod is 2 for both the Parent and the Child objects. There are a number of ways to fix this problem, depending on your control of the Parent/Child code.

First, if you have complete control, simply change the Child’s property to (assign) from (retain):

  1.  
  2. /* same parent class as before goes here.. */
  3.  
  4. //a child with a parent property
  5. @interface Child : NSObject
  6. {
  7.    NSObject * parent;
  8. }
  9.  
  10. @property (assign) NSObject * parent;
  11.  
  12. @end
  13.  
  14. (void) someMethod
  15. {
  16.   self.parent = [[[Parent alloc] init] autorelease]; //parent reference count 2 (init, and 'self' retain property assignment..)
  17.   Child * child = [[[Child alloc] init] autorelease]; //child reference count 1
  18.   parent.child = child; //child reference count 2
  19.   child.parent = parent; //parent reference count still 2
  20.  
  21.   self.parent = nil; //parent reference count 1
  22.   //later autorelease will see parent has ref count of 1, and clean it up, causing parent to clean up child in dealloc..
  23. }

If you don’t have control of the child code, such as when you have some UIView subclass w/ a delegate pointing to yourself, be careful to clean up that delegate reference:

  1. (void) unloadMyTable
  2. {
  3.   self.tableView.delegate = nil;
  4.   [self.tableView removeFromSuperview];
  5.   self.tableView = nil;
  6. }

Many of the stock objective c cocoa library delegate properties are (assign) rather than (retain), but some delegate class references such as UIWebViewDelegate explicitly warn you to set the .delegate to nil before deallocating a view, which may imply that view has a retain hold on your delegate implementation.

Not So Obvious Memory Problem #3: UIView children

Be careful with the UIView objects you add as children to another UIView:

  1. (void) loadMoreImages
  2. {
  3.    //be careful to get rid of old view before creating new one
  4.    [self.child removeFromSuperview];
  5.    self.child = nil;
  6.  
  7.    //make new child view
  8.    self.child = [[UIView alloc] initWithFrame:CGRectMake(0,0,0,0)] autorelease]];
  9.    [self addSubview:self.child];
  10. }

If you consistently add new views as children to another view that won’t go away anytime soon, those child views will stack up. It is not enough to hold a property to one of the child views and nil it out, you must remember to also call removeFromSuperview on old child views you intend to deallocate.

Obvious Memory Management Issues: Dealloc

Now, the obvious stuff. Make sure you have a dealloc that sets anything you hold onto with a (retain) property to nil:

  1. (void)dealloc
  2. {    
  3.     self.label = nil;
  4.     self.viewButton = nil;
  5.     [super dealloc];
  6. }

Also be careful to call [super dealloc]; at the end of all of your dealloc methods. Failing to do so will prevent the super class (such as UIView) from cleaning up it’s various child objects that it retains.

Memory management in objective-c isn’t always easy, or straight forward. Hopefully some of these tips will help you in your coding adventures.