Search code examples
iphonecocoa-touchmemory-managementinterface-buildernib

If you have an IBOutlet, but not a property, is it retained or not?


I find the documentation on this issue to be unclear:

Say you are working with iOS (NOT the Mac case, no need to mention the differences). Say it is strictly 4.0+ (no need to mention differences in old OS). Say we are loading the NIB strictly automatically.

Say you have a UIViewController, BigView. Say there are a dozen so-called "top-level" items in the NIB file...could be custom controls, images, or anything else.

Say you are definitely going to explicitly create and then get rid of BigView a number of times during the app's run. So:

For one of these top-level items in the NIB, there are three possibilities:

(1) You do not have any sort of IBOutlet for it, at all.

(2) You do have a connected IBOutlet - but not a property.

(3) You do have a connected IBOutlet property (to avoid confusion, we'll say a retain property).

So what happens to the item when BigView is released?

In the case of (3) it seems clear that you must release explicitly. If you do not, it will hang around after the view is gone. No problem.

In the case of (1) I assume (but can anyone actually confirm?) that the item will be released when BigView is gone.

In the case of (2) it's not clear what happens.......

Looking at the well-known reference link: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html it is very dubious:

"In iOS, the nib-loading code uses the setValue:forKey: method to reconnect each outlet. That method similarly looks for an appropriate accessor method and [SO WHAT HAPPENS IF THERE ISN'T ONE?? TELL US APPLE...] falls back on other means when that fails...[GOOD GRIEF!]"

And scroll down to "Nib Object Retention":

"Objects in the nib file are created with a retain count of 1 and then autoreleased" Fantastic..

But wait! Read on a few words...

however, ... which uses the available setter method or retains the object by default if no setter method is available

What are they talking about?

Do they mean that if no setter is available (ivar, but no property), that it is AGAIN RETAINED (other than the "retain" they just mention in the previous clause) --- or, are they just repeating themselves, i.e. the "retains the object by default" is the same "retain" they were talking about immediately previously ("created with a retain count of 1 and then autoreleased").

And why would they even mention the autorelease if that's not what happens?

Indeed -- if anyone actually specifically knows the answer to this question ...... how do you know?!? Did you ask DTS, or through testing, or? I suggest, the key documentation (just pasted in) is aggressively unclear.

Again - if you have an IBOutlet, but not a property, connected to a "top-level" object .. are you responsible for releasing it? Is it retained? in that situation?

For that matter .... merely in situation (1) is it absolutely the case that the thingy will be released when BigView goes away? I would certainly assume this is the case, but who knows?

The question is what happens if you DO use an IBOutlet iVar, but NOT a property...

I've foolishly never thought about this before / assumed too much, does anyone have the decisive answer? Cheers!!


For the record I have made a test project.

In fact (surprisingly to me) the mere act of connecting an IB element to an IBOutlet in fact apparently adds one retain.

(I can only assume from the shoddy docu, in that situation you get specifically: Retain, Autorelease, Retain - leading to one retain on balance.)

So, that's the answer.

I will post the demo project. I also direct any readers to Jonah's answer below which flawlessly explains the behavior of setValue:forKey: Cheers


Solution

  • I don't see what causes so much confusion, I think the "Nib Object Retention" documentation explains exactly what happens. Let's break it down and walk through what happens:

    Objects in the nib file are created with a retain count of 1 and then autoreleased.

    ClassLoadedFromNib *loadedObject = [[[ClassLoadedFromNib alloc] initWithCoder:coder] autorelease];
    

    As it rebuilds the object hierarchy, however, UIKit reestablishes connections between the objects using the setValue:forKey: method,

    [filesOwner setValue:loadedObject forKey:nameOfIBOutlet];
    

    which uses the available setter method or retains the object by default if no setter method is available.

    The default behavior of -setValue:forKey: in iOS is roughly

    //lazy pseudocode
    if ([self respondsToSelector:@selector(@"setKeyName:")]) {
      [self setKeyName:value];
    }
    else {
      object_setIvar(self, _keyName, [value retain]);
    }
    

    See the key-value programming guide for even more detail. Unless your file's owner object overrides -setValue:forKey: (or +accessInstanceVariablesDirectly and -setValue:forUndefinedKey: ) expect object ownership to be managed as above.


    If you define outlets for nib-file objects, you should always define a setter method (or declared property) for accessing that outlet. Setter methods for outlets should retain their values, and setter methods for outlets containing top-level objects must retain their values to prevent them from being deallocated.

    Allowing nib loading to set ivar directly to externally retained objects is confusing. Don't do that. Provide setter methods for your outlets so the ownership of the loaded object is clear.


    If you do not store the top-level objects in outlets, you must retain either the array returned by the loadNibNamed:owner:options: method or the objects inside the array to prevent those objects from being released prematurely.

    Objects not connected to outlets have been autoreleased. Retain them or the array returned from -loadNibNamed:owner:options: if you are going to try to access them later.