Mac Behaviour of awakeFromNib

DannySmurf

macrumors 6502a
Original poster
Jul 7, 2005
628
0
Okay, I've got a problem here that I just don't understand. Maybe this is just the assumed behaviour, because I can't find thing one about it in any of Apple's documentation or on the web, but I'm baffled as to why this would happen, or why anyone would choose this as the default behaviour.

I have two data objects in my app's controller class, folderList and dataStore. They're initialized and deserialized from disk in the class' init function. I'm also implementing awakeFromNib in this class.

Now, as soon as control exits awakeFromNib, both folderList and dataStore become invalid. They're not nil, but it's like they haven't been initialized at all. DURING execution of awakeFromNib, they're valid and populated. But as soon as control passes out of awakeFromNib, they're empty and uninitialized.

Can anyone shed some light on this? It seems to me that using awakeFromNib shouldn't be blasting the other data in my class, but like I said, maybe I'm not understanding how all the different initializations that go on in an app work.
 

HexMonkey

Administrator
Staff member
Feb 5, 2004
2,135
427
New Zealand
That sounds like an autorelease problem. When an object is autoreleased, it can be used in the current method, but will be released when control returns to the application object (when the autorelease pool is emptied). Most methods return autoreleased objects, so you will need to retain them if you want to use them outside the scope of the current method.

For more information about autorelease, see Memory Management: Object Ownership and Disposal.
 

DannySmurf

macrumors 6502a
Original poster
Jul 7, 2005
628
0
Maybe I should elaborate on that. I don't think this is an autorelease problem, because none of my objects are being autoreleased, and I'm not getting anything back from another function.

If I exclude the awakeFromNib (in other words, everything is done in the init function), things work fine. But as soon as I add awakeFromNib to my code -- even if I don't add any implementation (awakeFromNib is EMPTY) -- my objects are invalid once awakeFromNib returns.

I need to use awakeFromNib for some initialization on something in the NIB file, so the obvious solution (just don't use awakeFromNib) isn't an option.
 

DannySmurf

macrumors 6502a
Original poster
Jul 7, 2005
628
0
Here's the init function. folderList, dataStore and folderImage are all released in the class' release function.

Code:
- (id)init
{
	folderList = [[FolderList alloc] init];
	[folderList deserialize];
	
	dataStore = [[DataStore alloc] init];
	[dataStore deserialize];
	
	folderImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"folder" ofType:@"png"]];
	
	return self;
}
If I add this bit of code right above the init function, once initialization is finished and my code gets control, folderList and dataStore are empty (and consequently, the app that was working just fine using only init crashes in several places).

Code:
- (void)awakeFromNib
{
}
 

HexMonkey

Administrator
Staff member
Feb 5, 2004
2,135
427
New Zealand
After some experimenting, I think I found the problem. You say you are releasing your variables in the controller's release method, however you should put this in the class's dealloc method, not release. Sending a release message decrements the reference count of an object, and once this count reaches 0, release sends a dealloc message to the object. Therefore, if an object has been retained multiple times, sending a release method shouldn't cause the object's variables to be released. However, if you release your variables in the release method as you are doing, they will be unusable after the controller receives just one release message, even though its reference count might still be greater than 0.

The reason the application will only crash when you include awakeFromNib is that your controller is retained and released when the method is included. To fix your problem, just rename the release method to dealloc.

On another note, although unrelated to the problem, you're not calling [super init] from your init method. It's a good habit to write init methods in the following format, which could prevent a couple of potential problems in the future:

Code:
- init
{
    if (self = [super init]) {
        /* class-specific initialization goes here */
    }
    return self;
}
 

caveman_uk

Guest
Feb 17, 2003
2,391
1
Hitchin, Herts, UK
I was reading in Wil Shipley's blog that you shouldn't reassign self.
Don't assign "self = [super init]" in your init statements, ever. If you ever type "self =" YOU'VE DONE WRONG. I know there's some book out there that says to do it. It's wrong as well, so there. 'self' is assigned by the Obj-C machinery when -init is called. Re-assigning it should really be a compiler error, but it wasn't made so because back in 1989, when we started all this, we were using +new instead of +alloc, -init, and +new both allocated the memory AND initialized it, and so we assigned 'self' by hand in our +new methods. Nowadays, 'self' is set for you, and it's ugly and potentially hazardous and redundant to set it again.'
He says you should use a form along the lines of
-(id)init{

if (![super init])
return nil; // Bail out

// Rest of init code

return self;
}
This raised quite a few comments in the blog about whether this was right. To be honest I've always used the self=[super init] form and it's never bitten me. Wil is a rather opinionated so-and-so but he has an awful lot more experience than me so I'm prepared to believe him.

He also has interesting comments on the retain-release stuff you do in KVC setters. He says to check if the objects are equal before you do a retain-release as retain and release are 'expensive' and you want to do as little of that as you can.
- (void)_setNodeAlias: (BDAlias *)newNodeAlias;
{
if (_nodeAlias == newNodeAlias)
return;
[_nodeAlias release];
_nodeAlias = [newNodeAlias retain];
}
 

HexMonkey

Administrator
Staff member
Feb 5, 2004
2,135
427
New Zealand
Interesting read. His arguments appear to state that "self=" isn't necessary because only in extremely rare cases (which most programmers will never encounter) will [super init] return a different value. While this is true, using "self=" should never have any problems, and it is the method used in Apple documentation.

The way I see it, both methods will work and it's mostly a matter of personal preference which one you use, although "self=[super init]" may be slightly more future compatible.
 

caveman_uk

Guest
Feb 17, 2003
2,391
1
Hitchin, Herts, UK
He seems pretty certain...

From one of Wil's comments in his blog
self = [super init]

I must stress this again: this is not right based on anything said here. There may be some reason it's not right, but nobody has stated a reason that's convinced me.

Class-clusters: if you subclass a class cluster, the init method you call _WILL NOT_ return a different object than what you asked for. Period. It won't. If you want to argue this, let's do it, but you'll lose.

This is NOT to say that if you call an init... method on a class cluster when you ARE NOT subclassing it, that you'll get the same class of object that you originally allocated. You may not. That's what the documentation says.

Why?

Look, say I've got a class cluster, and the public class is Foo. Foo contains two ivars, so its structure looks like this:

Foo {
int badname;
int worsename;
}

Now, I define a subclass Bar of the class cluster Foo. Bar adds a new instance variable, "horriblename". This creates a C structure that looks like this:

Bar {
Foo {
int badname;
int worsename;
}
int horriblename;
}

Now, if I call:

Bar *thing = [[Bar alloc] init];

And Bar's init looks like:

- (id)init;
{
[super init];
horriblename=2.0;
return self;
}

I ABSOUTELY promise you that the Foo class cluster is NOT going to return me some other, private subclass of Foo. Because, if it did, the next line of code would crash.

In fact, "Foo" the abstract superclass exists for the _sole purpose_ of being subclassed. Its methods are defined inefficiently in terms of a couple base methods, so you can simply override those in your subclass and get a fully functional Foo-like object. If "Foo" returned any other KIND of object, your new code wouldn't be there.

Ok, so what if "Foo" correctly returned an object of type "Bar", but it wasn't the object you had just allocated? Well, then you'd have me. Except, WHY THE HECK WOULD IT DO THIS? How can an abstract superclass know of an instance in which it's more efficient to use some other object than the one you just gave it? I mean, it is, by definition, an ABSTRACT class. It really doesn't know a lot -- all its functionality is wrapped up in your implementation of two or three methods.

But, I tell you what. I like being right, it's true, but I like learning new things even more. So, I'll send $20, cash, to anyone who can find an instance in which they subclass a non-deprecated, Apple-supplied, 10.4 Cocoa class, call [super init] and DO NOT get back 'self'.

Limit one $20 prize per class, limit 5 classes total then I call it quits; first entry wins for any given class. Go for it. Prove me wrong. I'm excited!

Please note clearly that you have to be sending the init message to an instance of your subclass of a built-in Cocoa class.

I'll post the winners in a full blog entry, in which I will also apologize for my gross recklessness.
 

HexMonkey

Administrator
Staff member
Feb 5, 2004
2,135
427
New Zealand
It seems like he's arguing that "[super init]" is as good as "self=[super init]", while others claim it may not be. Without providing any examples of why leaving out "self=" is actually better (the performance loss (if any) of using "self=" is trivial and it's no less readable to anyone with much experience), I don't see any reason to change.

Claiming A is better than B because A is almost as good as B doesn't make much sense to me. :rolleyes:
 

HiRez

macrumors 603
Jan 6, 2004
5,812
1,724
Western US
One small reason I like "if (![super init]) { return nil; }" better is because it keeps all your initialization code buttud up as far left as possible instead of indented. And the setter check for equivalency first instead of just retaining the new value seems a tad silly as well. I really don't think it's that expensive to send a retain: message, all it's doing is incrementing an integer, right? Like the init thing, it's not wrong, but it doesn't seem necessarily superior. Wil certainly knows more about Objective-C than I do, but I also think he just likes to hear himself talk and he's a bit too aggressive in setting himself up as The Ultimate Programming Authority for my taste. :rolleyes:
 

HiRez

macrumors 603
Jan 6, 2004
5,812
1,724
Western US
HexMonkey said:
The reason the application will only crash when you include awakeFromNib is that your controller is retained and released when the method is included. To fix your problem, just rename the release method to dealloc.
And it's got to call [super dealloc] at the end. Doesn't he also need to retain those objects when he creates them in the init method?
 

HexMonkey

Administrator
Staff member
Feb 5, 2004
2,135
427
New Zealand
HiRez said:
One small reason I like "if (![super init]) { return nil; }" better is because it keeps all your initialization code buttud up as far left as possible instead of indented.
True, although you could combine the two with "if (!(self=[super init])) { return nil; }"

HiRez said:
Doesn't he also need to retain those objects when he creates them in the init method?
No, they are created with [[<Class> alloc] init], so don't need to be retained.
 

caveman_uk

Guest
Feb 17, 2003
2,391
1
Hitchin, Herts, UK
Wil certainly knows more about Objective-C than I do, but I also think he just likes to hear himself talk and he's a bit too aggressive in setting himself up as The Ultimate Programming Authority for my taste. :rolleyes:
I think you might just be right there ;)
 

DannySmurf

macrumors 6502a
Original poster
Jul 7, 2005
628
0
HexMonkey said:
After some experimenting, I think I found the problem. You say you are releasing your variables in the controller's release method, however you should put this in the class's dealloc method, not release.
Aha! It seems you're right. Changing it to dealloc fixed the problem. Looks like I've been going about my designs all wrong. The tutorials I've been reading have led me to believe that release is the "destructor" for a class and all the teardown code should go in there (I didn't even know there was such a thing as dealloc), and I've been writing all of my code on that assumption. I'll have to make some changes, apparently.
 

caveman_uk

Guest
Feb 17, 2003
2,391
1
Hitchin, Herts, UK
DannySmurf said:
Aha! It seems you're right. Changing it to dealloc fixed the problem. Looks like I've been going about my designs all wrong. The tutorials I've been reading have led me to believe that release is the "destructor" for a class and all the teardown code should go in there (I didn't even know there was such a thing as dealloc), and I've been writing all of my code on that assumption. I'll have to make some changes, apparently.
Check out this link for more info on this.