[Openmcl-devel] object classes and nib files (a clue and a puzzle)

Dan Knapp dankna at accela.net
Mon Jan 3 15:16:33 PST 2005

   Hmm, I see what you're saying.  In that case, I need to fix the docs, 
which means I
need to understand this better...

> - (id)initWithWindowNibName:(NSString *)windowNibName
>  Returns an NSWindowController object initialized with windowNibName, 
> the name of the nib file (minus the “.nib” extension) that archives 
> the receiver’s window. The windowNibName argument cannot be nil. Sets 
> the owner of the nib file to the receiver. The default initialization 
> turns on cascading, sets the shouldCloseDocument flag to NO, 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 

> 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

   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

> 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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: text/enriched
Size: 7961 bytes
Desc: not available
URL: <https://lists.clozure.com/pipermail/openmcl-devel/attachments/20050103/74d358af/attachment.bin>

More information about the Openmcl-devel mailing list