views:

647

answers:

6

While coding always the same questions concerning retain counts of IBOutlets came along: Retain count after unarchiving an object from NIB? When to use @property's for an IBOutlet? Retain or assign while setting? Differences between Mac and iPhone?

So I read The Nib Object Life Cycle from Apple's documentation. Some test apps on Mac and iPhone gave me some strange results. Nevertheless I wrote down a few rules how to handle this issue to stay happy while coding but now wanted to verify with the community and listen to your opinions and experiences:

  1. Always create an IBOutlet for top-level objects. For non-top-level objects if necessary (access needed).
  2. Always provide a property as follows for IBOutlets (and release them where necessary!):
    • Top-level objects on Mac:
      • @property (nonatomic, assign) IBOutlet SomeObject *someObject;
      • @synthesize someObject;
      • [self.someObject release];
    • Non-top-level objects on Mac (no release):
      • @property (nonatomic, assign) IBOutlet NSWindow *window;
      • @synthesize someObject;
    • Top-level objects on iPhone (must retain):
      • @property (nonatomic, retain) IBOutlet SomeObject *someObject;
      • @synthesize someObject;
      • [self.someObject release];
    • Non-top-level objects on iPhone (should retain):
      • @property (nonatomic, retain) IBOutlet UIWindow *window;
      • @synthesize window;
      • [self.window release];

Side notes:

  • On Mac and iPhone outlet connections are made with a setter if available.
  • Top-level objects: "have [...] no owning object"
  • Non-top-level objects: "any objects that have a parent or owning object, such as views nested inside view hierarchies."

So the question would be: is this correct and good practice?

I hope you can approve or correct it.

A: 

I can write my opinion about iPhone NIB development:

  • If you use IB then use as many IBOutlets as possible (sometimes you don't know the views hierarchy when you build a NIB - it may be dynamic) or don't use them at all - otherwise there will be a mess
  • Use properties only if you want to access the views from outside the View Controller (if they should be public)
  • AFAIK there's no need to manage memory for IBOutlets

Hope it helps...

Michael Kessler
You can have private properties too (properties not defined in the public .h header).
Kendall Helmstetter Gelner
Yes, I know. You also can add the "@private" before the members that should be private - this will turn the properties to private too. I wrote it regarding the IBOutlets. The only reason that I see to add a property to IBOutlet is the need to make them public...
Michael Kessler
@Raphael, as for your question regarding the "private class-local category extension" - this is a regular category that is declared in ".m" file. This way no other class knows about its existence (because you import the ".h" file). You can use this work-around (in my opinion this is a work-around) to declare private properties and methods. In addition, as I have already mentioned there is a better way to declare private properties (not methods) - by writing "@private" before the member (inside the @interface block)...
Michael Kessler
@Michael writing @private before an ivar won't make the according @property private, I could call it and access the ivar (private or not) from outside.
Raphael Schaad
@Raphael, I suppose you are right. Seems that the only reason to write @private is for restricting subclasses from using the ivar...
Michael Kessler
A: 

You should follow standard memory management guidelines. If your outlet is connected to a retained property, then you must release it in the -dealloc message.

And yes, you any top level objects not retained by any other objects usually need to be retained by yourself.

Nick Bedford
A: 

Top-level objects: "have [...] no owning object"

Nix. Top-level objects are owned by the File's Owner, which is the File's Owner because it owns all the top-level objects in the file.

Windows have that option to release themselves as a convenience, but I find my design cleaner (even if it's a little more work) when I either turn it off and manage its lifetime myself, just like any other object I own, or use a window controller.

If you think this conflicts with the documentation you were quoting, let's go through the entire paragraph:

Objects in the nib file are initially created with a retain count of 1. As it rebuilds the object hierarchy, however, AppKit autoreleases any objects that have a parent or owning object, such as views nested inside view hierarchies.

Thus killing off its own ownerships. The nib loader doesn't want to own your objects.

By the time the nib-loading code is done, only the top-level objects in the nib file have a positive retain count and no owning object. Your code is responsible for releasing these top-level objects.

In other words, it's handing the ownership over to you.

The curious artifact of that is that you'll actually leak the object if your property to it has retain semantics. The documentation says you should retain it:

For both Mac OS X and UIKit, the recommended way to manage the top-level objects in a nib file is to create outlets for them in the File’s Owner object and then define setter methods to retain and release those objects as needed.

But if you do this, the object will remain alive even after you release your ownership of it.

I think I'll go file a bug about this. (Edit: Done. x-radar://problem/7559755) At the very least, the nib loader shouldn't be handing off two retentions, which it does in my test app (on 10.5.8 and 10.6.1).

Peter Hosey
"But if you do this, the object will remain alive even after you release your ownership of it." No it will not. If you have an IBOutlet retain property, when the view goes away and you release your IBOutlets the object will be released. I don't see how you get anything else from the docs, and I know from practical experience this is how it works (in the phone).
Kendall Helmstetter Gelner
Yes it will, at least on current Mac OS X. Run my test app (the link to which I've just added). The doc seems to suggest this is intentional when it says the top-level objects “have a positive retain count and no owning object”, but I find it to be bogus behavior, I remember it being different long ago (under Tiger?), and it seems to contradict the later paragraph in my last quote, hence my bug report about it.
Peter Hosey
I don't see anything wrong or bogus when I try the test project. After fixing "release" in both classes to call [super release], the assign property ends with a retain count of one (which you then release!!!) and the retain property has a retain count of two. The NIB loader has a retain for all top level objects (that will get released when that gets unloaded) and your code then adds it's own retention atop that (or not as in the case of the assign property).
Kendall Helmstetter Gelner
Meant to say "code calling the nib loader" since there is other code actually loading in MainWindow.xib.
Kendall Helmstetter Gelner
D'oh. Thanks for pointing those bugs out; code updated. What exactly are you saying should be “unloaded”?
Peter Hosey
To wrap it up, in which case what should be done in your opinion? I've expected some strange retain counts on Mac and iPhone as well when I tested the cases, thus the doubt if my rules are good practice (I didn't thought there could be a bug nobody has found until now).
Raphael Schaad
Retain the object. It's the path that will cause the least pain if I'm right and the extra retention that the nib loader gives you is a bug. If you have a single place where you're done with the object and you *know* nothing else will be holding on to it, you can add code that releases the top-level object `retainCount` times to avoid the leak— **generally a very bad idea**, but in this case, the only effective (and, with the `retainCount` count-down, future-proof against Apple fixing the bug) workaround. I'd also recommend filing your own bug report and mentioning the bug number of mine.
Peter Hosey
A: 

1) In general, why would you have a top-level object with no IBOutlet to point to it anyway? That requirement has never seemed very restrictive.

2) I think you got the settings about right for the iPhone. You can also use an assign property on the iPhone as well, which does what you would expect... but in general after a lot of use I prefer to use retain properties so I am 100% clear on when I consider the object released (especially with the viewDidUnload method to implement).

Also, just as a side note it's not good form to call [self.property release]. That leaves the reference intact but potentially invalid, if something else ever also releases the object... either say self.property = nil, or (better) set the underlying class variable to nil directly without using properties in dealloc statements (to avoid any possible side effects in dealloc).

As I mentioned in response to another poster, you can keep things clean by using IBOutlet properties declared in private class-local category extensions so they are not public properties. That looks like:

// in .m file
@interface MyClass ()
@property (nonatomic, retain) IBOutlet UIView *myPrivateView;
@end

@implementation MyClass
@synthesize myPrivateView;
.....
Kendall Helmstetter Gelner
Thanks for pointing out the issue with the way I release the objects. I'll consider that in the future. So releasing it (via property or not) and then setting it to nil is the good practice?Does IB recognize the private properties for connecting the outlets in IB? Could you please explain the "private class-local category extension"-thingy in short? I've never seen the syntax with empty braces so far. When to use?
Raphael Schaad
I guess your code above won't work since IB only parses the .h file for the IBOutlet tag and you can't connect these properties. Correct me if I'm wrong.
Raphael Schaad
+8  A: 

Always have your nibs' File's Owner be a subclass of NSWindowController or NSViewController (on Mac OS X) or UIViewController (on iPhone), and use @property (retain) IBOutlet for all of its outlets, doing appropriate releases in your controller subclass -dealloc method.

This pattern will work fine on both Mac OS X and iPhone OS, because NSWindowController and NSViewController on Mac OS X take implicit ownership of top-level objects for you (and relinquish that in their own -dealloc methods), and iPhone OS doesn't take any implicit ownership of top-level objects for you during nib loading.

Chris Hanson
This is a very clear answer and I thank you a lot (I don't have enough reputation to vote it up though). This should be mentioned in Apple's docs! One question: there shouldn't be a case where the class of a NIBs' File's Owner can't be a sublcass of either NSWindowController, NSViewController or UIViewController, right?
Raphael Schaad
File's Owner can be a subclass of NSObject or any subclass, if you want it to be. It's really up to you. However, modern Cocoa development with NSWindowController, NSViewController, UIViewController et al is structured around letting these controllers manage their nib's top-level resources.
Chris Hanson
A: 

From apple's doc mentioned above:

For both Mac OS X and UIKit, the recommended way to manage the top-level objects in a nib file is to create outlets for them in the File’s Owner object and then define setter methods to retain and release those objects as needed. Setter methods give you an appropriate place to include your memory-management code, even in situations where your application uses garbage collection. One easy way to implement your setter methods is to create a declared property (using the @property syntax) and let the compiler create them for you. For more information on how to define properties, see The Objective-C Programming Language.

Otherwise use @property(nonatomic, retain) IBOutlet * outletName;

Garry