Previous Section Next Section Table of Contents Glossary Index

Chapter 14. The Objective-C Bridge

14.6. Defining Objective-C Methods

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.

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

14.6.2. Using objc:defmethod

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.

14.6.3. Method Redefinition Constraints

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