Previous Section | Next Section | Table of Contents | Glossary | Index |
In Objective-C, unlike in CLOS, every method belongs to some particular class. This is probably not a strange concept to you, because C++ and Java do the same thing. When you use Lisp to define Objective-C methods, it is only possible to define methods belonging to Objective-C classes which have been defined in Lisp.
You can use either of two different macros to define methods
on Objective-C classes. define-objc-method
accepts a two-element list containing a message selector name
and a class name, and a body. objc:defmethod
superficially resembles the normal
CLOS defmethod
, but creates methods on
Objective-C classes with the same restrictions as those created
by define-objc-method
.
As described in the section Calling Objective-C Methods, the names of Objective-C methods are broken into pieces, each piece followed by a parameter. The types of all parameters must be explicitly declared.
Consider a few examples, meant to illustrate the use
of define-objc-method
. Let us define a
class to use in them:
(defclass data-window-controller (ns:ns-window-controller) ((window :foreign-type :id :accessor window) (data :initform nil :accessor data)) (:metaclass ns:+ns-object))
There's nothing special about this class. It inherits from
ns:ns-window-controller
. It has two slots:
window
is a foreign slot, stored in the Objective-C
world; and data
is an ordinary slot, stored
in the Lisp world.
Here is an example of how to define a method which takes no arguments:
(define-objc-method ((:id get-window) data-window-controller) (window self))
The return type of this method is the foreign type :id,
which is used for all Objective-C objects. The name of the
method is
get-window
. The body of the method is the
single line (window self)
. The
variable self
is bound, within the body, to
the instance that is receiving the message. The call
to window
uses the CLOS accessor to get the
value of the window field.
Here's an example that takes a parameter. Notice that the name of the method without a parameter was an ordinary symbol, but with a parameter, it's a keyword:
(define-objc-method ((:id :init-with-multiplier (:int multiplier)) data-window-controller) (setf (data self) (make-array 100)) (dotimes (i 100) (setf (aref (data self) i) (* i multiplier))) self)
To Objective-C code that uses the class, the name of this
method is initWithMultiplier:
. The name of
the parameter is
multiplier
, and its type
is :int
. The body of the method does some
meaningless things. Then it returns
self
, because this is an initialization
method.
Here's an example with more than one parameter:
(define-objc-method ((:id :init-with-multiplier (:int multiplier) :and-addend (:int addend)) data-window-controller) (setf (data self) (make-array size)) (dotimes (i 100) (setf (aref (data self) i) (+ (* i multiplier) addend))) self)
To Objective-C, the name of this method is
initWithMultiplier:andAddend:
. Both
parameters are of type :int
; the first is
named multiplier
, and the second
is addend
. Again, the method returns
self
.
Here is a method that does not return any value, a so-called
"void method". Where our other methods
said :id
, this one
says :void
for the return type:
(define-objc-method ((:void :take-action (:id sender)) data-window-controller) (declare (ignore sender)) (dotimes (i 100) (setf (aref (data self) i) (- (aref (data self) i)))))
This method would be called takeAction:
in Objective-C. The convention for methods that are going to be
used as Cocoa actions is that they take one parameter, which is
the object responsible for triggering the action. However, this
method doesn't actually need to use that parameter, so it
explicitly ignores it to avoid a compiler warning. As promised,
the method doesn't return any value.
There is also an alternate syntax, illustrated here. The following two method definitions are equivalent:
(define-objc-method ("applicationShouldTerminate:" "LispApplicationDelegate") (:id sender :<BOOL>) (declare (ignore sender)) nil) (define-objc-method ((:<BOOL> :application-should-terminate sender) lisp-application-delegate) (declare (ignore sender)) nil)
The macro OBJC:DEFMETHOD
can be used to
define Objective-C methods. It looks superficially like
CL:DEFMETHOD
in some respects.
Its syntax is
(OBC:DEFMETHOD name-and-result-type ((receiver-arg-and-class) &rest other-args) &body body)
name-and-result-type
is either an
Objective-C message name, for methods that return a value of
type :ID
, or a list containing an
Objective-C message name and a foreign type specifier for
methods with a different foreign result type.
receiver-arg-and-class
is a two-element
list whose first element is a variable name and whose second
element is the Lisp name of an Objective-C class or metaclass.
The receiver variable name can be any bindable lisp variable
name, but SELF
might be a reasonable
choice. The receiver variable is declared to be "unsettable";
i.e., it is an error to try to change the value of the
receiver in the body of the method definition.
other-args
are either variable names
(denoting parameters of type :ID
) or
2-element lists whose first element is a variable name and
whose second element is a foreign type specifier.
Consider this example:
(objc:defmethod (#/characterAtIndex: :unichar) ((self hemlock-buffer-string) (index :<NSUI>nteger)) ...)
The method characterAtIndex:
, when
invoked on an object of
class HEMLOCK-BUFFER-STRING
with an
additional argument of
type :<NSU>integer
returns a value of
type
:unichar
.
Arguments that wind up as some pointer type other
than :ID
(e.g. pointers, records passed by
value) are represented as typed foreign pointers, so that the
higher-level, type-checking accessors can be used on arguments
of
type :ns-rect
, :ns-point
,
and so on.
Within the body of methods defined
via OBJC:DEFMETHOD
, the local function
CL:CALL-NEXT-METHOD
is defined. It isn't
quite as general as CL:CALL-NEXT-METHOD
is
when used in a CLOS method, but it has some of the same
semantics. It accepts as many arguments as are present in the
containing method's other-args
list and
invokes version of the containing method that would have been
invoked on instances of the receiver's class's superclass with
the receiver and other provided arguments. (The idiom of
passing the current method's arguments to the next method is
common enough that the CALL-NEXT-METHOD
in
OBJC:DEFMETHODs
should probably do this if
it receives no arguments.)
A method defined via OBJC:DEFMETHOD
that returns a structure "by value" can do so by returning a
record created via MAKE-GCABLE-RECORD
, by
returning the value returned
via CALL-NEXT-METHOD
, or by other similar
means. Behind the scenes, there may be a pre-allocated
instance of the record type (used to support native
structure-return conventions), and any value returned by the
method body will be copied to this internal record instance.
Within the body of a method defined
with OBJC:DEFMETHOD
that's declared to
return a structure type, the local macro
OBJC:RETURNING-FOREIGN-STRUCT
can be used
to access the internal structure. For example:
(objc:defmethod (#/reallyTinyRectangleAtPoint: :ns-rect) ((self really-tiny-view) (where :ns-point)) (objc:returning-foreign-struct (r) (ns:init-ns-rect r (ns:ns-point-x where) (ns:ns-point-y where) single-float-epsilon single-float-epsilon) r))
If the OBJC:DEFMETHOD
creates a new
method, then it displays a message to that effect. These
messages may be helpful in catching errors in the names of
method definitions. In addition, if
a OBJC:DEFMETHOD
form redefines a method in
a way that changes its type signature, Clozure CL signals a
continuable error.
Objective C was not designed, as Lisp was, with runtime redefinition in mind. So, there are a few constraints about how and when you can replace the definition of an Objective C method. Currently, if you break these rules, nothing will collapse, but the behavior will be confusing; so don't.
Objective C methods can be redefined at runtime, but their signatures shouldn't change. That is, the types of the arguments and the return type have to stay the same. The reason for this is that changing the signature changes the selector which is used to call the method.
When a method has already been defined in one class, and you define it in a subclass, shadowing the original method, they must both have the same type signature. There is no such constraint, though, if the two classes aren't related and the methods just happen to have the same name.
Previous Section | Next Section | Table of Contents | Glossary | Index |