Re: half-initialized objects during decoding (Re: Persistance)
Re: half-initialized objects during decoding (Re: Persistance)
- Subject: Re: half-initialized objects during decoding (Re: Persistance)
- From: Chris Kane <email@hidden>
- Date: Thu, 11 Oct 2001 14:18:29 -0700
On Thursday, October 11, 2001, at 05:47 AM, Marco Scheurer wrote:
On Wed, 10 Oct 2001 13:21:01 -0700
Chris Kane <email@hidden> wrote:
The 'half-initialized object' problems I was referring to
I describe below. [...]
It seems to me that you can force yourself into the same
situation in other cases:
@implementation A
- init
{
...
b = [[B alloc] initWithA:self];
...
}
@end
@implementation B
- initWithA:(A *) anA
{
...
x = [anA someValue];
...
}
@end
If anA is half-initialized, x's value could be junk. I think
that in that case it is obviously the programmer's
responsibility not to create this kind of coupling or to
order messages so that "it works". Why should the
unarchiving situation be different?
This is true. However, it's more common to be receiving
half-initialized objects back from the unarchiver than to get them
passed in as parameters in the ordinary course of executing the program.
It's interesting to think about fixing this because it is hard to detect
that it is going on, as well -- there's no way to detect that the object
you've gotten back from -decodeObject is done initializing, today.
Since the returned object from that method is autoreleased, it is
natural to want to send it -retain, if nothing else, but even that may
not be safe. Particularly when the cycle involves more than two
nodes/levels in the object graph, it can be quite a mysterious problem.
On the other hand, there are several such issues of this general nature
(cyclic +initialize methods is another one that bites people) that don't
have solutions other than the programmer rewriting to avoid the
problem. If we could fix it while we're changing the general coding
mechanism, or by suggesting a strategy developers should use when
modifying their encode/decode methods to take advantage of the named
values anyway (though you obviously won't have to do that), then that
would be nice.
"Erik M. Buck" <email@hidden> wrote:
Cocoa uses the same technique. That is what -awakeFromNib
and -awakeAfterUsingCoder: are for. The -initWithCoder: method should be
implemented to do as little as possible and make few assumptions about
the
state of referenced objects. Final initialization based of referenced
objects should take place in an awake method.
In the current NSUnarchiver,
1) -awakeAfterUsingCoder: is called immediately after -initWithCoder:,
to allow awakeAfterUsingCoder: to substitute a new object; nothing in
the middle of being unarchived, except the object receiving
-awakeAfterUsingCoder:, is any more initialized in
-awakeAfterUsingCoder: than it was in -initWithCoder:.
2) -awakeAfterUsingCoder: doesn't have access to all the encoded object
state that -initWithCoder: had; -initWithCoder: has consumed some of it,
which it may need to save for -awakeAfterUsingCoder: in some cases, even
if the state will eventually be thrown away (in cases where some actual
state of an object is synthesized from the encoded state, which is quite
common when dealing with old archived versions backwards compatibly);
this is awkward, but not the end of the world.
And, none of this discussion addresses the fact that although
-initWithCoder: and -awakeAfterUsingCoder: return id, and in theory
allow you to return a different instance, in practice you may well break
references that other classes of objects have to instances of yours if
you do so. There isn't much those other classes can do about the
actions of your class (and this applies to developers using Cocoa
classes which are returning different objects too). Delaying the
-awakeAfterUsingCoder:, like -awakeFromNib is, until the main decoding
is all done, would just exacerbate the replacement problem (if it
continued to return id and not void like -awakeFromNib). The capability
of replacement could be eliminated altogether; however, replacement is a
compelling feature -- people want to do that.
Back to Marco:
I would have imagined that initializing a retain count (to 1
rather than 0) should be left to allocWithZone, and that
init... methods should leave it untouched. This would be
coherent with all the doc I've seen about alloc and copy,
statements like (in Object Ownership and Disposal): "If you
create an object (using alloc or allocWithZone:) or copy an
object (using copy, copyWithZone:, mutableCopy, or
mutableCopyWithZone:), you alone are responsible for
releasing it. If you didn't directly create or copy the
object, you don't own it and shouldn't release it." This
cannot work if init methods mess with retain counts.
Some of this has already been addressed by other mail. One of the
reasons for the +alloc/-init split is that, in some 'pure' OO sense,
class methods should not be fiddling with the internal data of class
instances. Plus, I think you're taking the doc a bit too literally
here -- they're saying "alloc or allocWithZone:", but a paired
initializer with those is also assumed, and is the thing which does the
actual initialization. "alloc or allocWithZone:" are mentioned because
those are the markers you'd see in your code to indicate that a retain
is coming back (as opposed to an expression like: [NSString
stringWithCString:"foo"];).
Chris Kane
Cocoa Frameworks, Apple