A common question from people new to Objective-C is “why do I have to separately alloc
and init
? Why can’t I just call one thing and get a fully initialized object?”.
It’s certainly not how other environments do it. Usually, you call one function or class method, and you get an object. Cocoa makes you send two messages: one to the class, then another to the fresh, uninitialized instance you got back. What does Cocoa know that those other environments don’t?
There are two reasons why alloc
and init
are separate:
-
“NSResponder”* says that sometimes, NeXT wanted to initialize the same object multiple times.
This is interesting, but horrifying to modern readers. Practically all init
methods I’ve seen do not handle the case of initializing the already-initialized; they blindly assign newly created/retained objects to the ivars without checking for existing objects there.
A bigger problem is when the init
method releases the receiver and returns some other object; in the second message, the “other” object may in fact be the receiver (returned from the first message), so [self release]
this time would be killing off an object that should stay alive. On the other hand, retaining the object before re-initializing it will create a leak if the object doesn’t release itself. Unpredictable behavior is bad.
-
On the same Stack Overflow question, Darren points out that alloc
is shorthand for allocWithZone:
; if allocation and initialization were merged in Cocoa, then, to maintain the same functionality, every initializer would need to take a zone parameter, and optionally come in a flavor without one. You can imagine how a proliferation of initializers would ensue.
This is essentially what the documentation says: “Separating allocation from initialization gives you individual control over each step so that each can be modified independently of the other.” I.e., both steps can be customized, so they must remain separate so that you can customize either or both.
So, there are two practical reasons why Cocoa’s designers separated allocation from initialization.
But let’s revisit this with modern eyes:
- As I mentioned, in modern Cocoa, initializing an instance again is risky at best. In practice, I don’t think anybody does it, and if anybody does, they probably feel guilty about it and mean to refactor that code “someday”.
- Nobody uses zones anymore.
That blows those two reasons away.
So, let’s imagine what life would be like if they had never existed in the first place, and allocation and initialization had remained one task:
+ (id) newWithFramistan:(Framistan *)framistan {
id obj = [super new];
if (obj) {
obj->framistan = framistan;
}
return obj;
}
Right away, you can see several advantages:
- We can name our local variable “framistan”, not “newFramistan” or anything like that.
- No more assignment in condition (
if ((self = …))
).
- No more assignment to
self
(which should make Wil Shipley happy).
- Eliminates the problem of
[super init]
returning a different object, whereupon you would have to release the receiver. Since the receiver is the class, there is nothing for the upstream-initialized object to be different from. If the superclass wants to return an existing object, it can just do that.
- Related to the first point, we no longer have a name conflict between instance variables and local (especially argument) variables. In
init
methods, we resolve this conflict with style rules, such as the aforementioned newFramistan
for locals or m_framistan
for instance variables; in this newWithFramistan:
method, the conflict doesn’t exist in the first place.
- There is never an uninitialized object that the caller could leave lying around. We’ve all seen (or written) code like
Foo *foo = [Foo alloc]; [foo init];
; in this alternate universe, such faux pas are impossible.
There are trade-offs:
- It’s one line longer. (Depending on your employers’ level of cluefulness, this may be a small advantage.)
- Since you’re not in an instance, you have to prefix every instance-variable access with
obj->
. I can see how this could get tedious; on the other hand, this is what prevents ivar-vs.-local name conflicts.
- If you use object controllers that automatically prepare their content, they’ll use
alloc
and init
, bypassing this initializer. I’ve never used the automatic-content-preparation feature, so this doesn’t affect me.
- It’ll be less than familiar to anybody who hasn’t read this post, and it’s certainly a change from currently-typical Cocoa code.
- Doing this in a subclass of a class that has an unusual designated initializer, such as NSView with its
initWithFrame:
, could get tricky.
My opinion is mixed. On the one hand, real problems with the init
way of doing things are rare for anyone who knows the ropes. On the other hand, it sure is more straightforward, isn’t it?
What do you think?