Hmm, I see what you're saying. In that case, I need to fix the
docs, which means I
need to understand this better...
Courier-
(id)initWithWindowNibName:(0000,0000,EEEDNSString
*)windowNibName
Times
Returns an NSWindowController object initialized with
windowNibName, the name of the nib file (minus the
“Courier.nibTimes”
extension) that archives the receiver’s window. The
windowNibName argument cannot be
CouriernilTimes.
Sets the owner of the nib file to the receiver. The default
initialization turns on cascading, sets the
Courier0000,0000,EEEDshouldCloseDocumentTimes
flag to
CourierNOTimes,
and sets the autosave name for the window’s frame to an empty string.
Hmmm. Yeah, that implies it would work without :owner.... I wonder
why my code
didn't, then. I guess I need to go resurrect my old test case and
find out...
which seems to suggest that you are allowed to drop the
:owner. I've also been digging into how nib files work (see below) and
I see that initWithWindowNibName
doesn't normal invoke an initialization callback so any lisp side
initialization is going to have to be done by make-instance or the
'alloc method. Maybe I'm missing something but I can't see why it
wouldn't work (and it does seem to work, since I've been doing lots of
coding this way).
Well, there *is* a Lisp wrapper object, and it is constructed at
some point; otherwise you
wouldn't be able to refer to the object at all. The question is just
why it isn't getting initialized.
With the standard-class metaclass, it would be the default method on
initialize-instance that
actually performed the initialization of the Lisp slots, and I would
be very surprised were that
not also the case with objc wrapper metaclasses. Maybe do (trace
initialize-instance) to
see whether it's getting called? (That could generate a very high
volume of debug output,
of course...)
Thinking about this did help me understand what was going on,
however.
With objc classes, the objc part gets initialized by the AppKit code,
but the lisp part has to be initialized by ALLOCATE-INSTANCE. Normally
this happens transparently when MAKE-INSTANCE calls ALLOCATE-INSTANCE
and then sends the 'alloc and 'init messages to AppKit to initialize
the other side.
Actually, no. Allocate-instance just reserves storage for the
slots; initialize-instance gives
them their bindings and values. The standard-class make-instance
simply invokes
allocate-instance and then initialize-instance; of course it's a
little more complicated with
objc classes, on account of the need to parse the arguments.
Loading instances from a nib file does not invoke
MAKE-INSTANCE, so ALLOCATE-INSTANCE is not invoked either. What you
get is a lisp object that correctly maps to a valid OBJC object but
has no lisp guts.
Hmm, yeah. Except with initialize-instance instead of
allocate-instance. Of course the
question then becomes: What values should the Lisp slots have? This
is not, after all,
a new object. Remember that a nib isn't necessarily constructed with
Interface Builder;
it can also be a snapshot of a running program. In the latter case,
it's quite likely that,
prior to being saved in the nib, the object had values which were not
the initial ones. So,
I don't think it would be correct to automatically call
initialize-instance on all
newly-awoken objects.
The nib system does actually have hooks to allow it to be extended,
so in principle it
would be possible to implement something that did save and restore
Lisp slots. This
would only be a very-long-term solution (nobody has time to develop it
right now), and
I'm not sure it would be desirable, but I'm noting the possibility in
case I'm the only one
who's thought of it.
This works fine for pure OBJC classes because the lisp slots
don't really exist anyway and accessing a slot gets magically turned
into an AppKit call. If a class has real lisp slots (not :foreign-type
definitions), however, then you end up with something that looks like
a class, receives OBJC messages but generates errors if the lisp slots
are accessed because SLOT-BOUNDP can't locate the slot definition.
My hack was to make %OBJC-DOMAIN-SLOTS-VECTOR do slot vector
initialization on demand.
The clean solution, I think, would be to make use of the objc
awakeFromNib message.
This is sent immediately upon loading of the object, and yes, Lisp is
quite able to receive
it. There ought to be, in Lisp, a default method on awake-from-nib
which would call
allocate-instance. It should *not* call initialize-instance; any such
behavior, if desired,
should be defined by the author of the specific class, with another
method on
awake-from-nib.
That would probably be a very small piece of code. The main thing
I'm not sure about
is how to define it as a default to be used everywhere it's
appropriate. Randy? Any
advice?
This doesn't really fix the problem, though, because
INITIALIZE-INSTANCE doesn't get called, either.
As I said above, I don't think it's appropriate to call
initialize-instance by default. If you
wanted to call it for your class, you would just do:
(define-objc-method ((:void awake-from-nib) alex-class)
(initialize-instance self))
For a short-term solution, this is also where you should call
(allocate-instance); in the
solution I suggest, you would instead begin this with
(call-next-method), and then the
default method would call (allocate-instance). I'm not sure, though,
whether you can
safely do this or whether it will try to allocate and initialize the
foreign slots as well.
If you try it, tell me if it works... I gather that you have just
read through the code for
allocate-instance, so perhaps you can answer that better than I can.
I think that the right thing to do would be to call
ALLOCATE-INSTANCE and INITIALIZE-INSTANCE whenever we try to access
and unitialized OBJC instance, but I'm not clear on where the test
would go.
That's probably a much more difficult test to make, and in what
sense is it uninitialized?
You'd have to do a heuristic check, since there's no general way to
tell; also, it would be
a performance hit at every access. There is no such behavior for
native Lisp objects;
you're quite free, as I read the standard, to call allocate-instance
at one point and then,
much later, call initialize-instance. The behavior if you tried to
access the slots in
between would be undefined, but, in practice, would just be an
unbound-slot error. So
Lisp isn't necessarily set up to make a test like that possible.
Anyway, it's not necessary
because you can use awake-from-nib.
Hmmm. I understand what's going on now; thank you. Before I
correct the docs, I'd like
to either have a best-practice to recommend, or see the problem fixed
entirely... Hrm.
-- Dan Knapp