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: Marco Scheurer <email@hidden>
- Date: Fri, 12 Oct 2001 11:52:33 +0200
On Thursday, October 11, 2001, at 11:18 pm, Chris Kane wrote:
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:
[...]
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.
Why?
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.
Maybe a solution then would be to make it easier to detect the problem.
Maybe something like assertions that could be turned on or off, even
though bracketing initWithCoder methods in initDidStart / initDidFinish
could be cumbersome.
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.
I still think it could be safe by initializing the retain count before
anything else. Just something one needs to be aware. Surely, this kind
of temporal coupling is not great, but it happens.
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.
I'm still unconvinced that a solution is needed, especially if it
involves trade offs in performance, or if the cure is worse than the
problem and would make writing initWithCoder a pain, as you suggested in
a previous message.
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.
Replacement can also be done in other init methods, so again the problem
is general.
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. [...]
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.
I agree, and it is a very valuable pattern. However this is not what is
done in the default case, NSObject:
NSLog (@"%d", [[NSObject alloc] retainCount]);
prints "1".
Of course, in the case of NSObject (implementation detail) the retain
count is not stored in the object's data, but nevertheless, it is alloc,
not init that initializes the retain count. All this can be considered
irrelevant because:
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"];).
Right, I also mentionned that in a previous message.
Marco Scheurer
Sen:te, Lausanne, Switzerland
http://www.sente.ch