1
votes

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.

6

6 Answers

10
votes

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.

1
votes

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).

1
votes

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;

0
votes

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...

0
votes

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.

0
votes

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;
.....