Previous Section | Next Section | Table of Contents | Glossary | Index |
In Objective-C, methods are called "messages", and there's a special syntax to send a message to an object:
[w alphaValue] [w setAlphaValue: 0.5] [v mouse: p inRect: r]
The first line sends the method "alphaValue" to the object
w
, with no parameters. The second line sends
the method "setAlphaValue", with the parameter 0.5. The third
line sends the method "mouse:inRect:" - yes, all one long word -
with the parameters p
and
r
.
In Lisp, these same three lines are:
(send w 'alpha-value) (send w :set-alpha-value 0.5) (send v :mouse p :in-rect r)
Notice that when a method has no parameters, its name is an ordinary symbol (it doesn't matter what package the symbol is in, as only its name is checked). When a method has parameters, each part of its name is a keyword, and the keywords alternate with the values.
These two lines break those rules, and both will result in error messages:
(send w :alpha-value) (send w 'set-alpha-value 0.5)
Instead of (send), you can also invoke (send-super), with the same interface. It has roughly the same purpose as CLOS's (call-next-method); when you use (send-super), the message is handled by the superclass. This can be used to get at the original implementation of a method when it is shadowed by a method in your subclass.
Clozure CL's FFI handles many common conversions between Lisp and foreign data, such as unboxing floating-point args and boxing floating-point results. The bridge adds a few more automatic conversions:
NIL is equivalent to (%NULL-PTR) for any message argument that requires a pointer.
T/NIL are equivalent to #$YES/#$NO for any boolean argument.
A #$YES/#$NO returned by any method that returns BOOL will be automatically converted to T/NIL.
Some Cocoa methods return small structures, such as those used to represent points, rects, sizes and ranges. When writing in Objective C, the compiler hides the implementation details. Unfortunately, in Lisp we must be slightly more aware of them.
Methods which return structures are called in a special way; the caller allocates space for the result, and passes a pointer to it as an extra argument to the method. This is called a Structure Return, or STRET. Don't look at me; I don't name these things.
Here's a simple use of this in Objective C. The first line sends the "bounds" message to v1, which returns a rectangle. The second line sends the "setBounds" message to v2, passing that same rectangle as a parameter.
NSRect r = [v1 bounds]; [v2 setBounds r];
In Lisp, we must explicitly allocate the memory, which is done most easily and safely with rlet. We do it like this:
(rlet ((r :<NSR>ect)) (send/stret r v1 'bounds) (send v2 :set-bounds r))
The rlet allocates the storage (but doesn't initialize
it), and makes sure that it will be deallocated when we're
done. It binds the variable r to refer to it. The call to
send/stret
is just like an ordinary call to
send
, except that r is passed as an extra,
first parameter. The third line, which calls
send
, does not need to do anything special,
because there's nothing complicated about passing a structure
as a parameter.
In order to make STRETs easier to use, the bridge provides two conveniences.
First, you can use the macros slet
and slet*
to allocate and initialize local
variables to foreign structures in one step. The example
above could have been written more tersely as:
(slet ((r (send v1 'bounds))) (send v2 :set-bounds r))
Second, when one call to send
is made
inside another, the inner one has an implicit
slet
around it. So, one could in fact
just write:
(send v1 :set-bounds (send v2 'bounds))
There are also several pseudo-functions provided for convenience by the Objective-C compiler, to make objects of specific types. The following are currently supported by the bridge: NS-MAKE-POINT, NS-MAKE-RANGE, NS-MAKE-RECT, and NS-MAKE-SIZE.
These pseudo-functions can be used within an SLET initform:
(slet ((p (ns-make-point 100.0 200.0))) (send w :set-frame-origin p))
Or within a call to send
:
(send w :set-origin (ns-make-point 100.0 200.0))
However, since these aren't real functions, a call like the following won't work:
(setq p (ns-make-point 100.0 200.0))
To extract fields from these objects, there are also some convenience macros: NS-MAX-RANGE, NS-MIN-X, NS-MIN-Y, NS-MAX-X, NS-MAX-Y, NS-MID-X, NS-MID-Y, NS-HEIGHT, and NS-WIDTH.
Note that there is also a send-super/stret
for use within methods. Like send-super
,
it ignores any shadowing methods in a subclass, and calls the
version of a method which belongs to its superclass.
There are a few messages in Cocoa which take variable numbers of arguments. Perhaps the most common examples involve formatted strings:
[NSClass stringWithFormat: "%f %f" x y]
In Lisp, this would be written:
(send (find-class 'ns:ns-string) :string-with-format #@"%f %f" (:double-float x :double-float y))
Note that it's necessary to specify the foreign types of the variables (in this example, :double-float), because the compiler has no general way of knowing these types. (You might think that it could parse the format string, but this would only work for format strings which are not determined at runtime.)
Because the Objective-C runtime system does not provide any information on which messages are variable arity, they must be explicitly declared. The standard variable arity messages in Cocoa are predeclared by the bridge. If you need to declare a new variable arity message, use (DEFINE-VARIABLE-ARITY-MESSAGE "myVariableArityMessage:").
The bridge works fairly hard to optimize message sends, when it has enough information to do so. There are two cases when it does. In either, a message send should be nearly as efficient as when writing in Objective C.
The first case is when both the message and the receiver's class are known at compile-time. In general, the only way the receiver's class is known is if you declare it, which you can do with either a DECLARE or a THE form. For example:
(send (the ns:ns-window w) 'center)
Note that there is no way in Objective-C to name the class of a class. Thus the bridge provides a declaration, @METACLASS. The type of an instance of "NSColor" is ns:ns-color. The type of the class "NSColor" is (@metaclass ns:ns-color):
(let ((c (find-class 'ns:ns-color))) (declare ((ccl::@metaclass ns:ns-color) c)) (send c 'white-color))
The other case that allows optimization is when only the message is known at compile-time, but its type signature is unique. Of the more-than-6000 messages currently provided by Cocoa, only about 50 of them have nonunique type signatures.
An example of a message with a type signature that is not unique is SET. It returns VOID for NSColor, but ID for NSSet. In order to optimize sends of messages with nonunique type signatures, the class of the receiver must be declared at compile-time.
If the type signature is nonunique or the message is unknown at compile-time, then a slower runtime call must be used.
When the receiver's class is unknown, the bridge's ability to optimize relies on a type-signature table which it maintains. When first loaded, the bridge initializes this table by scanning every method of every Objective-C class. When new methods are defined later, the table must be updated. This happens automatically when you define methods in Lisp. After any other major change, such as loading an external framework, you should rebuild the table:
? (update-type-signatures)
Because send
and its relatives
send-super
, send/stret
,
and send-super/stret
are macros, they
cannot be funcall
ed,
apply
ed, or passed as arguments to
functions.
To work around this, there are function equivalents to
them: %send
,
%send-super
,
%send/stret
, and
%send-super/stret
. However, these
functions should be used only when the macros will not do,
because they are unable to optimize.
Previous Section | Next Section | Table of Contents | Glossary | Index |