[Openmcl-devel] How to send a message to the objc super-class ?

Gary Byers gb at clozure.com
Sat Feb 9 17:03:10 PST 2008

On Sat, 9 Feb 2008, Didier Verna wrote:

> I wrote:
>> mikel evins <mevins at mac.com> wrote:
>>> We're working on it; in the meantime, can you use ccl::define-objc-
>>> method instead of objc:defmethod?
>>   Actually, I've now tried call-next-method, and ccl::define-objc-method
>> as follows:
>> (objc:defmethod (#/drawWithFrame:inView: :void)
>>     ((self outline-cell) frame view)
>>   (call-next-method frame view))
>> (ccl::define-objc-method ((:void :draw-with-frame (:id frame)
>>                            :in-view (:id view)) outline-cell)
>>   (ccl::send-super :draw-with-frame frame :in-view view))
>> Both compile and execute without any complaint, but nothing is drawn,
>> which suggests to me that the actual super-method isn't executed... ?
> I have made some progress on this. As I said, the following compiled but
> didn't draw anything:
> (objc:defmethod (#/drawWithFrame:inView: :void)
>    ((self outline-cell) frame view)
>  (call-next-method frame view))

When you defined that, you should have seen a warning of the form:

; Warning: previously declared methods on #1="drawWithFrame:inView:"
; all had the same type signature, but #<OBJC-METHOD-INFO
; -[OutlineCell #1#] #x3000421D518D> introduces ambiguity

There are lots of cases (at least, there are -some- cases) where the
types of non-specialized arguments (or of the return value) of
different methods on the same message.  This makes sense when the
classes of the specialized argument (the receiver) are disjoint:

(defclass apple (ns:ns-object)  ; NOT  a subclass of fruit, in this example
   (:metaclass ns:+ns-object))

(defclass orange (ns:ns-object)  ; NOT  a subclass of fruit either
   (:metaclass ns:+ns-object))

(objc:defmethod (#/seedCount :float) ((self apple))

(objc:defmethod (#/seedCount :int) ((self orange))

(let* ((x (#/seedCount y)))
   (type-of x)

will return different answers (and invoke different #/seedCount
methods) depending on what Y is.  (If I wasn't too lazy, I'd
try to make a more convincing example and make the types of the
non-specialized arguments different.)

In order for #/seedCount to invoke the appropriate method and arrange
to pass any other foreign arguments (if there were any) and accept
foreign results of the right type, it needs to know something about
the type of the specialized argument and do some runtime dispatching.
If all methods defined on a message have the same foreign argument and
result types, we can just ask the ObjC runtime to send the message
with the specialized argument (the "receiver") and any other foreign
arguments of indicated type and expect this to always return the same
result type.  Dealing with ambiguity is a little harder and may
indicate a bug (see below), if the introduction of ambiguity isn't
intentional, then paying attention to the warning is advised.  (Yes,
I recognize that just reading and parsing that particular warning
is harder than it could be.)

To continue the strained example, suppose that we decided that
the seeds of some kinds of apples (Mackintosh apples) could
only be enumerated if we defined some special structure type:

(def-foreign-type :seed_counter
   (:struct nil
      ;;; several fields defined here

(defclass mackintosh (apple)
   (:metaclass ns:+ns-object))

(objc:defmethod (#/seedCount :seed_counter) (self mackintosh)

Note that we now have two non-disjoint classes (mackintosh and apple)
which define type-ambiguous methods on #/seedCount).  That's a worse
kind of ambiguity than what existed when we just had methods on the
disjoint classes "apple" and "orange"; a caller (especially a
caller that's implemented in ObjC) might have known that 
[seedCount some_apple] would return a float, but the float it
gets back when it calls seedCount on this mackintosh object the
float that it gets back (if it doesn't crash) is kind of funny-looking.

If you're very sloppy about declarations, ObjC will let you do
this (define a method on a subclass whose types differ from
the superclass's definition), but it's not a particularly useful
thing to do.  Defining a method whose type signature differs from
the superclass's version is almost certainly something that you
don't want to do, and you should get a sterner warning or error
if you do something like my mackintosh example above.

Back to your example:

(objc:defmethod (#/drawWithFrame:inView: :void)
     ((self outline-cell) frame view)
   (call-next-method frame view))

The "frame" and "view" arguments are implicitly declared to be of type
:ID (which means "pointer to ObjC instance or class", roughly).  The
superclass's method declares the "frame" argument to be an :<NSR>ect;
that kind of conflict between the subclass and superclass methods
can't really be resolved, and you probably should have gotten a stern
warning or an error when this was defined.  (You probably actually
got a softer warning about the introduction of ambiguity.)

> I have discovered that I can further specify the type of the frame
> argument like this:
> (objc:defmethod (#/drawWithFrame:inView: :void)
>    ((self outline-cell) (frame :<NSR>ect) view)
>  (call-next-method frame view))
> and then, it works like a charm. Not sure if it's a bug or what.
>    But now, I'm hopelessly confused about some naming conventions. Here
> are a couple of questions related to type specifications in
> objc:defmethod's:

> 1/ should I use :bool or :<BOOL> ?

Since :<BOOL> (which is a way of representing what's called BOOL - all
in uppercase - in ObjC) is defined and :bool isn't, I'd vote for :<BOOL>

> 2/ should I use :void or :<VOID> ? (you get the idea)

Likewise, :void (which maps to [Obj]C's "void" is defined and has
meaning and :<VOID> isn't and doesn't.

> 3/ in the method above, I can specialize the frame argument to :<NSR>ect
>   (I even *have* to). However, if I want to specialize the view
>   argument to :<NSV>iew (or :<NSO>utline<V>iew in my case), I get this:
>> Error: Unknown foreign type: :<NSV>IEW
>> While executing: CCL::%PARSE-FOREIGN-TYPE, in process listener(1).

The first argument in an objc:defmethod - often called "the receiver"
in ObjC - is the only argument that's specialized (in the CLOS sense):
what method gets called depends on the class of the receiver/first arg
and does not depend on the types/classes of any other arguments.  In
an objc:defmethod form, the receiver's class must be specified:

(objc:defmethod (#/scrollOffTheScreen:afterDelay: :void)
     ((self misbehaving-view) (delay :<NST>ime<I>nterval))
   (sleep delay)
   (#/scrollOffScreen self))

You can call the receiver argument anything that you want to;
"self" is often a good choice, if only because the receiver is
magically called "self" in ObjC.

The second argument here isn't "specialized" in the CLOS sense;
we need to specify its type to satisfy [Obj]C calling conventions.
As a shorthand, we can declare a parameter of type :ID (the foreign
type of any ObjC instance or class) by just using the parameter
name, rather than a (<param> <foreign type>) 2-element list.

So, for the first (receiver) argument, the argument's (ObjC) class
must be specified: that argument (alone) is used to determine
method applicability.  For any other arguments (and for the return
value, if it's not :ID), the foreign type must be specified.

> 4/ In fact, it seems that I can't specialize the view argument to
>   anything at all; not even ns:ns-view.

You aren't really "specializing" the view argument.(or anything but
the first, receiver argument.)

The foreign type :ID is shorthand for "pointer to structure whose
first field is called "isa" - as in "X is a Y" - and is a pointer to 
a structure of type "objc_class").  :ID is shorter.

I think that ObjC 2.0 (used in 64-bit Leopard) is starting to 
take steps to make some of this more abstract and to hide the
fact that there are C structures involved under the hood.

At some level, an instance of NS:NS-VIEW might look something
like a pointer to a structure of type :<NSV>iew).

Pay no attention to the man behind the curtain:

? (pprint (ccl::load-record :<NSV>iew))
                          (:STRUCT :OBJC_CLASS
                           (:ISA (:* (:STRUCT :OBJC_CLASS)) 64)))
                          (:STRUCT :OBJC_OBJECT
                           (:ISA (:* (:STRUCT :OBJC_CLASS)) 64)))
                         (:STRUCT :<CGR>ECT
                           (:STRUCT :<CGP>OINT (:X DOUBLE-FLOAT 64)
                            (:Y DOUBLE-FLOAT 64))
                           (:STRUCT :<CGS>IZE (:WIDTH DOUBLE-FLOAT 64)
                            (:HEIGHT DOUBLE-FLOAT 64))
                        (:_BOUNDS (:STRUCT :<CGR>ECT) 256)
                        (:_SUPERVIEW (:* (:STRUCT :OBJC_OBJECT)) 64)
                        (:_SUBVIEWS (:* (:STRUCT :OBJC_OBJECT)) 64)
                          (:STRUCT :<NSW>INDOW
                           (:ISA (:* (:STRUCT :OBJC_CLASS)) 64)
                           (:_NEXT<R>ESPONDER (:* (:STRUCT :OBJC_OBJECT)) 64)
                           (:_FRAME (:STRUCT :<CGR>ECT) 256)
                           (:_CONTENT<V>IEW (:* (:STRUCT :OBJC_OBJECT)) 64)
                           (:_DELEGATE (:* (:STRUCT :OBJC_OBJECT)) 64)
                             (:STRUCT :<NSR>ESPONDER
                              (:ISA (:* (:STRUCT :OBJC_CLASS)) 64)
                              (:_NEXT<R>ESPONDER (:* (:STRUCT :OBJC_OBJECT))
                           (:_LAST<L>EFT<H>IT (:* (:STRUCT :<NSV>IEW)) 64)
                           (:_LAST<R>IGHT<H>IT (:* (:STRUCT :<NSV>IEW)) 64)
                           (:_COUNTERPART (:* (:STRUCT :OBJC_OBJECT)) 64)
                           (:_FIELD<E>DITOR (:* (:STRUCT :OBJC_OBJECT)) 64)
                           (:_WIN<E>VENT<M>ASK (:SIGNED 32) 32)
                           (:_WINDOW<N>UM (:SIGNED 64) 64)
                           (:_LEVEL (:SIGNED 32) 32)
                             (:STRUCT :<NSC>OLOR
                              (:ISA (:* (:STRUCT :OBJC_CLASS)) 64)))
                           (:_BORDER<V>IEW (:* (:STRUCT :OBJC_OBJECT)) 64)
                           (:_POSTING<D>ISABLED (:UNSIGNED 8) 8)
                           (:_STYLE<M>ASK (:UNSIGNED 8) 8)
                           (:_FLUSH<D>ISABLED (:UNSIGNED 8) 8)
                           (:_RESERVED<W>INDOW1 (:UNSIGNED 8) 8)
                           (:_CURSOR<R>ECTS (:* :VOID) 64)
                           (:_TRECT<T>ABLE (:* :VOID) 64)
                             (:STRUCT :<NSI>MAGE
                              (:ISA (:* (:STRUCT :OBJC_CLASS)) 64)
                                (:STRUCT :<NSS>TRING
                                 (:ISA (:* (:STRUCT :OBJC_CLASS)) 64)))
                              (:_SIZE (:STRUCT :<CGS>IZE) 128)
                               (:STRUCT :__IMAGE<F>LAGS (:SCALABLE :BIT 1)
                                (:DATA<R>ETAINED :BIT 1)
                                (:UNIQUE<W>INDOW :BIT 1)
                                (:SIZE<W>AS<E>XPLICITLY<S>ET :BIT 1)
                                (:BUILT<I>N :BIT 1) (:NEEDS<T>O<E>XPAND :BIT 1)
                                (:USE<EPSO>N<R>ESOLUTION<M>ISMATCH :BIT 1)
                                (:COLOR<M>ATCH<P>REFERRED :BIT 1)
                                (:MULTIPLE<R>ESOLUTION<M>ATCHING :BIT 1)
                                (:SUB<I>MAGE :BIT 1) (:ARCHIVE<B>Y<N>AME :BIT 1)
                                (:UNBOUNDED<C>ACHE<D>EPTH :BIT 1)
                                (:FLIPPED :BIT 1) (:ALIASED :BIT 1)
                                (:DIRTIED :BIT 1) (:CACHE<M>ODE (:UNSIGNED 2) 2)
                                (:SAMPLE<M>ODE (:UNSIGNED 2) 2)
                                (:FOCUSED<W>HILE<P>RINTING :BIT 1)
                                (:IMAGE<E>FFECTS<R>EQUESTED :BIT 1)
                                (:IS<T>EMPLATE :BIT 1)
                                (:FAILED<T>O<E>XPAND :BIT 1)
                                (:RESERVED1 (:BITFIELD 9) 9))
                              (:_REPS (:* (:STRUCT :OBJC_OBJECT)) 64)
                               (:* (:STRUCT :_<NSI>MAGE<A>UXILIARY)) 64)))
                           (:_UNUSED (:SIGNED 32) 32)
                             (:STRUCT :<NSM>UTABLE<S>ET
                              (:ISA (:* (:STRUCT :OBJC_CLASS)) 64)))
                             (:STRUCT :<NSURL>
                              (:ISA (:* (:STRUCT :OBJC_CLASS)) 64)
                              (:_URL<S>TRING (:* (:STRUCT :<NSS>TRING)) 64)
                              (:_BASE<URL> (:* (:STRUCT :<NSURL>)) 64)
                              (:_CLIENTS (:* :VOID) 64)
                              (:_RESERVED (:* :VOID) 64)))
                           (:_SIZE<L>IMITS (:* (:STRUCT :<CGS>IZE)) 64)
                           (:_FRAME<S>AVE<N>AME (:* (:STRUCT :<NSS>TRING)) 64)
                             (:STRUCT :<NSS>ET
                              (:ISA (:* (:STRUCT :OBJC_CLASS)) 64)))
                            (:STRUCT :__W<F>LAGS (:BACKING (:UNSIGNED 2) 2)
                             (:VISIBLE :BIT 1) (:IS<M>AIN<W>INDOW :BIT 1)
                             (:IS<K>EY<W>INDOW :BIT 1)
                             (:HIDES<O>N<D>EACTIVATE :BIT 1)
                             (:DONT<F>REE<W>HEN<C>LOSED :BIT 1)
                             (:ONE<S>HOT :BIT 1) (:DEFERRED :BIT 1)
                             (:CURSOR<R>ECTS<D>ISABLED :BIT 1)
                             (:HAVE<F>REE<C>URSOR<R>ECTS :BIT 1)
                             (:VALID<C>URSOR<R>ECTS :BIT 1)
                             (:DOC<E>DITED :BIT 1)
                             (:DYNAMIC<D>EPTH<L>IMIT :BIT 1)
                             (:WORKS<W>HEN<M>ODAL :BIT 1)
                             (:LIMITED<B>ECOME<K>EY :BIT 1)
                             (:NEEDS<F>LUSH :BIT 1)
                             (:VIEWS<N>EED<D>ISPLAY :BIT 1)
                             (:IGNORED<F>IRST<M>OUSE :BIT 1)
                             (:REPOSTED<F>IRST<M>OUSE :BIT 1)
                             (:WINDOW<D>YING :BIT 1) (:TEMP<H>IDDEN :BIT 1)
                             (:FLOATING<P>ANEL :BIT 1)
                             (:WANTS<T>O<B>E<O>N<M>AIN<S>CREEN :BIT 1)
                             (:OPTIMIZED<D>RAWING<O>K :BIT 1)
                             (:OPTIMIZE<D>RAWING :BIT 1)
                             (:TITLE<I>S<R>EPRESENTED<F>ILENAME :BIT 1)
                             (:EXCLUDED<F>ROM<W>INDOWS<M>ENU :BIT 1)
                             (:DEPTH<L>IMIT (:UNSIGNED 4) 4)
                             (:DELEGATE<R>ETURNS<V>ALID<R>EQUESTOR :BIT 1)
                             (:LMOUSEUP<P>ENDING :BIT 1)
                             (:RMOUSEUP<P>ENDING :BIT 1)
                             (:WANTS<T>O<D>ESTROY<R>EAL<W>INDOW :BIT 1)
                             (:WANTS<T>O<R>EG<D>RAG<T>YPES :BIT 1)
                             (:SENT<I>NVALIDATE<C>URSOR<R>ECTS<M>SG :BIT 1)
                             (:AVOIDS<A>CTIVATION :BIT 1)
                             (:FRAME<S>AVED<U>SING<T>ITLE :BIT 1)
                             (:DID<R>EG<D>RAG<T>YPES :BIT 1)
                             (:DELAYED<O>NE<S>HOT :BIT 1)
                             (:POSTED<N>EEDS<D>ISPLAY<N>OTE :BIT 1)
                             (:POSTED<I>NVALID<C>URSOR<R>ECTS<N>OTE :BIT 1)
                             (:INITIAL<F>IRST<R>ESPONDER<T>EMP<S>ET :BIT 1)
                             (:AUTODISPLAY :BIT 1)
                             (:TOSSED<F>IRST<E>VENT :BIT 1)
                             (:IS<I>MAGE<C>ACHE :BIT 1)
                             (:INTERFACE<S>TYLE (:BITFIELD 3) 3)
                             (:KEY<V>IEW<S>ELECTION<D>IRECTION (:UNSIGNED 2) 2)
                              :BIT 1)
                             (:DEFAULT<B>UTTON<C>ELL<KED>ISABLED :BIT 1)
                             (:MENU<H>AS<B>EEN<S>ET :BIT 1)
                             (:WANTS<T>O<B>E<M>ODAL :BIT 1)
                             (:SHOWING<M>ODAL<F>RAME :BIT 1)
                             (:IS<T>ERMINATING :BIT 1)
                             (:WIN32<M>OUSE<A>CTIVATION<I>N<P>ROGRESS :BIT 1)
                             (:MAKING<F>IRST<R>ESPONDER<F>OR<M>OUSE<D>OWN :BIT
                             (:NEEDS<Z>OOM :BIT 1)
                             (:SENT<W>INDOW<N>EEDS<D>ISPLAY<M>SG :BIT 1)
                             (:LIVE<R>ESIZE<A>CTIVE :BIT 1))
                           (:_DEFAULT<B>UTTON<C>ELL (:* (:STRUCT :OBJC_OBJECT))
                           (:_INITIAL<F>IRST<R>ESPONDER (:* (:STRUCT :<NSV>IEW))
                            (:* (:STRUCT :<NSW>INDOW<A>UXILIARY)) 64)))
                        (:_G<S>TATE (:* (:STRUCT :OBJC_OBJECT)) 64)
                        (:_FRAME<M>ATRIX (:* (:STRUCT :OBJC_OBJECT)) 64)
                        (:_DRAW<M>ATRIX (:* (:STRUCT :OBJC_OBJECT)) 64)
                        (:_DRAG<T>YPES (:* (:STRUCT :OBJC_OBJECT)) 64)
                        (:_VIEW<A>UXILIARY (:* (:STRUCT :_<NSV>IEW<A>UXILIARY))
                         (:STRUCT :__<VF>LAGS (:ABOUT<T>O<R>ESIZE :BIT 1)
                          (:RETAIN<C>OUNT<O>VER<M>AX :BIT 1)
                          (:RETAIN<C>OUNT (:BITFIELD 6) 6)
                          (:INTERFACE<S>TYLE1 :BIT 1)
                          (:SPECIAL<A>RCHIVING :BIT 1)
                          (:NEEDS<D>ISPLAY<F>OR<B>OUNDS :BIT 1)
                          (:INTERFACE<S>TYLE0 :BIT 1)
                          (:REMOVING<W>ITHOUT<I>NVALIDATION :BIT 1)
                          (:NEEDS<B>OUNDS<C>HANGE<N>OTE :BIT 1)
                          (:BOUNDS<C>HANGE<N>OTES<S>USPENDED :BIT 1)
                          (:FOCUS<C>HANGE<N>OTES<S>USPENDED :BIT 1)
                          (:NEEDS<F>RAME<C>HANGE<N>OTE :BIT 1)
                          (:FRAME<C>HANGE<N>OTES<S>USPENDED :BIT 1)
                          (:NO<V>ERTICAL<A>UTOSIZING :BIT 1)
                          (:NEW<GS>TATE :BIT 1) (:VALID<GS>TATE :BIT 1)
                          (:NEEDS<D>ISPLAY :BIT 1) (:WANTS<GS>TATE :BIT 1)
                          (:AUTORESIZE<S>UBVIEWS :BIT 1)
                          (:AUTOSIZING (:BITFIELD 6) 6)
                          (:ROTATED<O>R<S>CALED<F>ROM<B>ASE :BIT 1)
                          (:ROTATED<F>ROM<B>ASE :BIT 1))
                         (:STRUCT :__<VF>LAGS2
                          (:NEXT<K>EY<V>IEW<R>EF<C>OUNT (:BITFIELD 14) 14)
                          (:PREVIOUS<K>EY<V>IEW<R>EF<C>OUNT (:BITFIELD 14) 14)
                          (:IS<V>ISIBLE<R>ECT :BIT 1) (:HAS<T>OOL<T>IP :BIT 1)
                          (:NEEDS<R>EAL<L>OCK<F>OCUS :BIT 1)
                          (:MENU<W>AS<S>ET :BIT 1))
                         32)) #x300040F8BDCD>

This is a structure type whose first field is named "isa" and which is
a pointer to an objc_class, so the type describing a pointer to an
:<NSV>iew structure is compatible with the foreign type :ID.  It's
generally true that the less we know about the :<NSV>iew structure,
the better; we'd rather think of NSView instances as abstract entities
and don't really want to peek inside at all that gunk ...

It might be nice if there was an abstract, opaque foreign type :<NSV>iew,
defined as "the structure type :<NSV>iew, whatever that is.  If the
ObjC frontend ever defines this type, the translator doesn't see that
definition.  The result is that you -could- declare the foreign type
of an NSView parameter as:

   (view (:* (:struct :<NSV>iew)))

though it's arguably not desirable to know that there's such a thing
as an :<NSV>iew structure type.

We could define a foreign type for every ObjC class (and say only
that it's equivalent to :ID), so we'd at least have the option
of saying

   (view :<NSV>iew)

for a non-specialized parameter.  (That wouldn't be semantically
different than saying:

   (view :id)

or just


but it may make the code more readable to human readers and might help
to enforce typechecking someday.  The types of all method arguments
other than the first one have nothing to do with what method is
applicable: they just have to do with how arguments are passed between
lisp and foreign code.  Anything that's of type :ID would be passed
the same way as anything else of type :id.

> 5/ On the other hand, I'm using ns:ns-outline-view as a type specializer
>   in some other method somewhere (that's a different naming
>   convention), and it works well.
> Please help me; I'm lost :-)

I think of ObjC messages as being sort of like constrained generic
functions, where (a) the methods are always specialized on their
first parameter and (b) there's a lot more concern about foreign
argument and return value types than there is in Lisp.  It's several
notches above what you'd get from just doing foreign function calls
and callbacks and it's dynamic enough to allow subclassing and
runtime extension, but it's several notches below lisp and CLOS
in terms of type-safety and dynamic typing (you can tell that
an NSString isn't an NSView, but you can't really tell whether
something's an :<NSR>ect or an integer at runtime, so you have
to be very careful to ensure that caller and callee agreee on 
that sort of thing.

> -- 
> Resistance is futile. You will be jazzimilated.
> Didier Verna, didier at lrde.epita.fr, http://www.lrde.epita.fr/~didier
> EPITA / LRDE, 14-16 rue Voltaire   Tel.+33 (0)1 44 08 01 85
> 94276 Le Kremlin-Bicêtre, France   Fax.+33 (0)1 53 14 59 22  didier at xemacs.org
> _______________________________________________
> Openmcl-devel mailing list
> Openmcl-devel at clozure.com
> http://clozure.com/mailman/listinfo/openmcl-devel

More information about the Openmcl-devel mailing list