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

alex crain alexcrain at mail.widgetworks.com
Mon Jan 3 17:18:57 PST 2005



On Jan 3, 2005, at 6:16 PM, Dan Knapp wrote:

>
>   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...)

I think that I understand why it works the way that it does:

Lisp classes are all subclasses of OBJC classes and if you go through 
the trouble of creating
a custom lisp class, you are probably going to instance it as well. 
Since the lisp class is created
first, the OBJC component is always mapped to an existing object, and 
everything works as
advertised. The first exception to this rule would be when people 
started using nib files, but all
the nib examples are pure OBJC classes that simply get a list wrapper.

In my case, I'm actually creating lisp classes from inside of OBJC 
code. Who would have thought of that?
MAKE-INSTANCE isn't called because lisp doesn't even know that the 
instance exists until it's
referenced - and then we reasonably assume that if we have a reference 
to an instance that
it's been created (how else would we have a reference?)

I'm thinking that somewhere there is a piece of code that does this:
    discover foreign reference
    map reference to class definition
    lookup reference in an instance list and not find it.

If anyone knows where that piece of code is, please share.

>
>> 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.

ALLOCATE-INSTANCE creates the slot definition forms as well, which are 
used by SLOT-VALUE
and SLOT-BOUNDP to figure out how to test the slot for a value. That 
was what was generating the
'class X has not slots error".

>> 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.

I'm not sure that this is a real issue because regardless of where nib 
files come from, there
is no practical way for a nib file to hold a lisp value. foreign-type 
slots are handled correctly
because it is assumed that initialization has already taken place on 
the OBJC side. We can
assume that lisp slots are always undefined because the nib file has no 
way of storing the
value if they wern't.

In any case, it's even worse then that. There is no support for method 
qualifiers with OBJC messages,
so you can't easily create an awakeFromNib method that wouldn't 
override/be overridden
by code put in by the programmer. (you could add gross hooks to 
%pascal-functions%....yuk).
Remember that awakeFromNib must be defined as a proper objc-method or 
it won't be called
at all (loadNibFile checks before calling. How it checks, I have no 
idea, but it does).

Also, awakeFromNib is called after initWithWhatever, IFF 
initWithWhatever is defined, and both
methods assume that ALLOCATE-INSTANCE has already been called. You 
could add a default
init handler to every class, but loadNibFile actually looks for the 
presence of the method to decide
whether to call it - it may end up calling the wrong method instead of 
what the programmer intended
simply because it's there.

What does work is

(define-objc-method ((:id :init-with-frame (:<NSR>ect frame))
		     hemlock-text-view)
   (initialize-instance self)
   (send-super :init-with-frame frame)
   self)

but this also means that we call INITIALIZE-INSTANCE twice if the class 
is created with
(MAKE-INSTANCE 'myclass :with-frame frame)

calling INITIALIZE-INSTANCE twice doesn't seem to do any damage besides 
wasted
cycles, but it's still wrong.

>
>   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.

You're not. It would be very cool to create lispy palettes and create 
pre-initialized lisp
objects in interface builder, but I don't think that it's going to 
happen because we would
need to link interface builder to the lisp world somehow.

It's conceivable that one could write an OBJC library that would open a 
listener connection
to a running lisp process and be able to evaluate lisp expressions but 
I think that in the
end it would be so confusing that no one would actually use it :-)

What is doable is writing palettes in OBJC that create lisp objects 
with initialized :foreign slots
with matching lisp modules to do the lisp side initialization. I 
haven't actually written a palette
yet, but it's supposed to be pretty straightforward.
>
>> 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
> _______________________________________________
> Openmcl-devel mailing list
> Openmcl-devel at clozure.com
> http://clozure.com/mailman/listinfo/openmcl-devel




More information about the Openmcl-devel mailing list