UIViewController retain/release dance in IB

With impending iOS 5 release, Object-C memory management is going to change for good. With all the release/retain/autorelease gone, it will be even more of no-brainer than it is now. But just in case it might help some, I would like to lay out a particular experience I have had with my code.

I assume you are already familiar with Object-C memory management scheme; when to owe, when to release, and when to retain. When you are to, however, use Interface Builder heavily for fast paced projects, you often have to let IB handle all the initial construction of a complex view controller that contains a lot of sub-views and even child view controllers. These child view controllers are usually called “nested UIViewController” and one of 2011 WWDC session video explicitly talks about that. Nested UIViewControllers can have their own .nib/.xib file so that you can build very complex view hierarchy out of collection of simple view controllers. Not to mention that you can reuse the simple view controllers however you see fit.

In that case, the parent/host view controller might get retained in the shade. Eventually when you touch “Back” arrow button on top of screen, the parent view controller gets “popped” or “dismissed” from navigation controller, but it will never be deallocated. This is called “zombie” object. Especially when nested view controllers take the parent view controller as a delegate or some sort of referral in IB, it could be just so much pain in your private part if you are not careful.

Parent-Child view controller connection in IB example

The side effect of having deallocated but retained zombie UIViewController somewhere in your memory is really bad. It will get you all kinds of colorful pain; EXC_BAD_ACCESS will greet you, NSZombie sometimes won’t find you a clue, memory leak of course, even didRecieveMemoryWarning() gets called on supposedly dead objects. Your app crashes on your face.

I am going to demonstrate three cases where nested view controllers take their parent view controller as a delegate. Each case differs slightly in taking the delegate and the outcomes in the end are of course different.

1. A retain delegate property in interface of a nested view controller.

@interface RetainDelegateViewController : UIViewController
@property (nonatomic, retain) IBOutlet id delegate;
@end

Then I connect the delegate IBOutlet to its parent view controller in IB, and my parent view controller gets retained one more time. If I forget to release somehow, it never gets deallocated when it gets popped from navigation controller. So, it exists somewhere in memory as a zombie, does all the things a live object is supposed to, and crashes your app since it is flagged to be deallocated but not really deallocated because of the one extra retain.

This is just as obvious as a flying flag. I would highly discourage you if you are doing it.

2. An IBOutlet delegate of a nested view controller without being a property.

@interface PlainDelegateViewController : UIViewController
{
	IBOutlet id delegate;
}
@end

This is common for subviews and buttons. Simply because it does not send extra “retain” message to your subviews and buttons, you might have better chance of not leaking memory. When I connect the delegate to its parent view controller in IB, however, this does not go in the way I believe. This yields exactly the same result as a retain property does. This is really confusing so I browse through object-c documents but no use. If anyone could point me a right point to look, I would really appreciate.

3. An assign property in interface of a nested view controller.

@interface AssignDelegateViewController : UIViewController
@property (nonatomic, assign) IBOutlet id delegate;
@end

This clears all the issues I have with previous two, and deallocate the parent view controller as soon as it pops off navigation controller.

TL;DR
When you are to refer your parent view controller from a nested view controller, always use assign property. You can download IBDelegateRetainTest from Github to see how different the outcomes are.

IBDelegateRetainTest

*There are three view controllers. When they are popped from navigation controller, they are supposed to be deallocated. Take a look at console log and find out which one gets really deallocated. ;)