google
yahoo
bing

Upcoming Classes

RSS Feeds

Categories

Archive

Site search

Real iPhone Crap 1: UINibLoading uses Key-Value Coding

Last week, my colleague Joe Conway wrote a posting suggesting that dot-notation was not a great addition to the Objective-C language and that he felt that programmers should not use it.  There was outrage. I, myself, was shocked that people cared at all.  After all, there are some examples of truly stupid stuff that Apple has done that are being misused in genuinely dangerous ways by the iPhone developer community.  These are worthy of discussion.  So, I’m doing a multi-part feature that I will call “Real iPhone Crap,” and this is the first installment.


On the desktop, when a nib file is loaded, outlets are set in a sensible way: to set an outlet called foo, the nib loader looks for an accessor called setFoo:. If it is unable to find the accessor,  the nib loader sets the variable foo directly.  This sounds like key-value coding, right?  It isn’t.  The important difference is that nib loading treats foo as a weak reference; the object it points to is not retained.

Thus, if you create a subclass of NSViewController that has a dozen outlets to subviews, only the top-level view is retained.  So, when the view controller is deallocated, it releases the top-level view and all the subviews are automatically deallocated.  Tidy!

On the phone, however, the nib loader uses key-value coding to set the outlets; By default, outlets are treated as strong references.  If you don’t have an accessor for your outlet, the view it refers to is retained.

Thus, if you create a subclass of UIViewController that has a dozen outlets to subviews, all of the subviews are retained.  When the view controller is deallocated,  the top-level view is released automatically, but you (in dealloc) still need to explicitly release all the subviews that you have outlets to.

But it is even worse than that on the phone because that top-level view is, by default, released if it is offscreen when a memory warning arrives.  So, the view may be destroyed and recreated many times during the life of the view controller.  You must also explicitly release the retained subviews in viewDidUnload. (OK, I just reread this and realized that the reader might get the idea that the retained views are leaking every time the view is unloaded. It is not quite that bad. A retained view will linger until the view is reloaded. At that point, it will be released as a side-effect of the setValue:forKey: that gets called when the outlet is set again.)

The use of key-value coding in nib loading is idiotic.  (Way more idiotic than dot-notation.)  There was a way that worked fine for 15 years, and Apple loused it up.  This stupidity and the developer community’s misunderstanding of it is the largest source of memory leaks on the phone.

There are two possible ways to fix this memory leak:

  • Release all your outlets in dealloc and viewDidUnload (Make sure you set them to nil in viewDidUnload.)
  • Make your outlets weak references

I haven’t seen many programmers using the second option, so let me be more explicit.  If you have an outlet called crapButton, declare it thusly:

@interface CrapViewController : UIViewController
{
    IBOutlet UIButton *crapButton;
}
@property (nonatomic, assign) IBOutlet UIButton *crapButton;

Then, in CrapViewController.m:

@synthesize crapButton;

Now that button is not being retained. (You will need to be sure that view is loaded before you can send any messages to this button.)

One of the nice things about not being an Apple employee anymore, is that I can tell my students honestly: “This is crap, and this is the way we work around it.” I hope this series proves useful to those who have not been able to attend our Beginning iPhone Bootcamp (for beginning programmers) or our iPhone Bootcamp (for experienced programmers).

Comments

Comment from Thomas Alvarez
Time: August 12, 2009, 9:51 am

This is an awesome post, Aaron! I’m finally learning iPhone development from the Apress book “Beginning iPhone 3 Development” and they talk about the first method you had.

For your method of weak outlet references, can you give a code example of how to “be sure that view is loaded before you can send any messages to this button.”?

Comment from Kendall Helmstetter Gelner
Time: August 12, 2009, 1:30 pm

There was a question on this at the end of the ViewController session at WWDC (asked by myself), and the people on stage thought the assign would not work – to the great consternation of a lot of people ,including myself, that had been doing this for a while… there was also someone in the lab who came down to asked why his code worked using assign references!

I did a small test project to prove what you say is the case though – if you use an assign property for IBOutlets the objects are still retained properly (or else you’d see a crash so pretty obviously it works!).

In Apple’s defense for this behavior, I would say that a memory leak now is better than a crash – if it behaved the old way I think you’d see a lot more applications crash as soon as they got a memory warning. The way it is now, they just leak a little (and as you note, some of those leaks are cleaned up anyway when the view comes back).

Thanks for clearing that the distinction though, as I had assumed Mac development would behave exactly the same as the iPhone has. Good to be aware of as I move to building out a few Mac applications.

One thing to be aware of is that in some cases you still may be better off using option 1, because you are more explicit in releasing values and can always set your references to nil when you release (a good practice for any class local variable). In some cases if you are not careful (like the view going away) your references can be invalid with assign variables and you’ll crash if you access them. But that’s just another way to say what you already said, clear out your references in viewDidUnload.

So the new pattern to use would be, to have a method that sets IBOutlet references to nil that gets called by dealloc and viewDidUnload (or also releases them if you are not using assign references).

Comment from Scott Newman
Time: August 13, 2009, 2:43 pm

Aaron, thanks for this great information.

For those of us who come from dynamic language backgrounds and who are still getting up to speed, could you explain what a strong vs a weak reference is?

Comment from Administrator
Time: August 13, 2009, 6:14 pm

@Scott: A “reference” is just a fancy name for a pointer. In a reference counted system, a “Strong Reference” is where the object at the other end of the pointer is retained. A “Weak Reference” is where the object at the other end of the pointer is not retained.

Some people are terrified of weak references because they create the possibility of a dangling pointer (a pointer to a freed object).

In reality, weak references are common in Cocoa: The notification center knows its observers through weak references. Delegate pointers are weak references.

I like Kendall’s approach: If you are really worried about the dangling pointers, just set them to nil in viewDidUnload.

Comment from Andy Warwick
Time: August 26, 2009, 12:27 pm

Pretty new to iPhone dev, and Cocoa in general.

Should I be releasing *everything* in viewDidUnload and Dealloc, or only IBOUtlets?

And then only in subclasses of UIViewController?

So far I’ve assumed that pretty much everything that’s got a @synthesize in the top of a file must have a corresponding release in dealloc. Does this advice mean I stick a entry to release and nil all of them in the viewDidUnload method as well, regardless of the whether they are IBOutlets and what the file subclasses?

– foo.h

@property (nonatomic, retain) IBOutlet UILabel *label;
@property (nonatomic, retain) NSString *labelText;

– foo.m

@synthesize label;
@synthesize labelText;

- (void)viewDidUnload {
[label release];
self.label = nil;
[labelText release];
self. labelText = nil;
}

- (void)dealloc {
[label release];
[labelText release];
[super dealloc];
}

Comment from Mark Kieling
Time: September 7, 2009, 11:38 am

Andy, since your properties are synthesized, the setter will release for you. So you only need to self.label = nil to get the ivar released and set to nil.

Comment from Vladimir Vildavski
Time: September 17, 2009, 10:05 pm

Thanks for the post. This is a hard-to-find level of detail.
It sounds like there is no difference in retaining the outlets between the desktop and the phone as long as the outlet is set up as a property. If the property is retaining, you have to deal with releasing it explicitly when necessary on both platforms. Therefore if one needs outlets as properties, declaring them as assigned makes sense in any case. (Properties representing the top-level nib objects should still be retaining)
The real difference is when the outlet is left as an ivar. In this regard, is this a general feature of key-value coding that retains an object when setting it to an ivar, or the ivar should be declared as IBOutlet to trigger this retaining behaviour?

Comment from Tino
Time: June 23, 2010, 3:40 pm

Nice post – sad story… I’ve already seen statements like “anObject.release;” in real-world applications, and I have to agree that dot-notation is everything else but perfect – you still have to change you code in three(!) locations for a property:
Declare the ivar, declare the property and synthesize it…

For me, it does not make much sense to make IBOutlets public at all – they should be private to the controller, and that worked fine on the Mac. I really don’t like view tags, too, but at least you can be sure that the nib is loaded when you access subviews with their tag…

I really don’t understand Apple’s motivation to change the scheme for the iPhone, and I doubt that they will ever “fix” it (I guess it’s more likely that the same method will be used for Mac-development sooner or later…)

Write a comment