All Manuals > LispWorks Objective-C and Cocoa Interface User Guide and Reference Manual > 1 Introduction to the Objective-C Interface

1.4 Defining Objective-C classes and methods

The preceding sections covered the use of existing Objective-C classes. This section describes how to implement Objective-C classes in Lisp.

1.4.1 Objects and pointers

When an Objective-C class is implemented in Lisp, each Objective-C foreign object has an associated Lisp object that can obtained by the function objc-object-from-pointer. Conversely, the function objc-object-pointer can be used to obtain a pointer to the foreign object from its associated Lisp object.

There are two kinds of Objective-C foreign object, classes and instances, each of which is associated with a Lisp object of some class as described in the following table:

Objective-C objects and associated Lisp objects
Objective-C typeFLI type descriptorClass of associated Lisp object

Class

objc-class

standard-class

id

objc-object-pointer

subclass of standard-objc-object

The implementation of an Objective-C class in Lisp consists of a subclass of standard-objc-object and method definitions that become the Objective-C methods of the Objective-C class.

1.4.2 Defining an Objective-C class

An Objective-C class implemented in Lisp and its associated subclass of standard-objc-object should be defined using the macro define-objc-class. This has a syntax similar to cl:defclass, with additional class options including :objc-class-nameto specify the name of the Objective-C class.

If the superclass list is empty, then standard-objc-object is used as the default superclass, otherwise standard-objc-object must be somewhere on class precedence list or included explicitly.

For example, the following form defines a Lisp class called my-object and an associated Objective-C class called MyObject.

(define-objc-class my-object ()
  ((slot1 :initarg :slot1 :initform nil))
  (:objc-class-name "MyObject"))

The class my-object will inherit from standard-objc-object and the class MyObject will inherit from NSObject. See 1.4.4 How inheritance works for more details on inheritance.

The class returned by (find-class 'my-object) is associated with the Objective-C class object for MyObject, so:

(objc-object-pointer (find-class 'my-object))

and:

(coerce-to-objc-class "MyObject")

will return a pointer to the same foreign object.

When an instance of my-object is made using make-instance, an associated foreign Objective-C object of the class MyObject is allocated by calling the class's "alloc" method and initialized by calling the instance's "init" method. The :init-function initarg can be used to call a different initialization method.

Conversely, if the "allocWithZone:" method is called for the class MyObject (or a method such as "alloc" that calls "allocWithZone:"), then an associated object of type my-object is made.

Note: If you implement an Objective-C class in Lisp but its name is not referenced at run time, and you deliver a runtime application, then you need to arrange for the Lisp class name to be retained during delivery. See define-objc-class for examples of how to do this.

1.4.3 Defining Objective-C methods

A class defined with define-objc-class has no methods associated with it by default, other than those inherited from its ancestor classes. New methods can be defined (or overridden) by using the macros define-objc-method for instance methods and define-objc-class-method for class methods.

Note that the Lisp method definition form is separate from the class definition, unlike in Objective-C where it is embedded in the @implementation block. Also, there is no Lisp equivalent of the @interface block: the methods of an Objective-C class are just those whose defining forms have been evaluated.

When defining a method, various things must be specified:

For example, a method that would be implemented in an Objective-C class as follows:

@implementation MyObject
- (unsigned int)areaOfWidth:(unsigned int)width
                height:(unsigned int)height
{
  return width*height;
}
@end

could be defined in Lisp for instances of the MyObject class from 1.4.2 Defining an Objective-C class using the form:

(define-objc-method ("areaOfWidth:height:" (:unsigned :int))
    ((self my-object)
     (width (:unsigned :int))
     (height (:unsigned :int)))
  (* width height))

The variable self is bound to a Lisp object of type my-object, and width and height are bound to non-negative integers. The area is returned to the caller as a non-negative integer.

1.4.3.1 Special method argument and result conversion

For certain types of argument, there is more than one useful conversion from the FLI value to a Lisp value. To control this, the argument specification can include an arg-style, which describes how the argument should be converted. If the arg-style is specified as :foreign then the argument is converted using normal FLI rules, but by default certain types are converted differently:

Special argument conversion for define-objc-method
Argument typeSpecial argument behavior

cocoa:ns-rect

The argument is a vector.

cocoa:ns-point

The argument is a vector.

cocoa:ns-size

The argument is a vector.

cocoa:ns-range

The argument is a cons.

objc-bool

The argument is nil or t.

objc-object-pointer

Depending on the Objective-C class, allows automatic conversion to a string or array.

objc-c-string

The argument is a string.

Likewise, result conversion can be controlled by the result-style specification. If this is :foreign then the value is assumed to be suitable for conversion to the result-type using the normal FLI rules, but if result-style is :lisp then additional conversions are performed for specific values of result-type:

Special result conversion for define-objc-method
Result typeSpecial result types supported

cocoa:ns-rect

The result can be a vector.

cocoa:ns-point

The result can be a vector.

cocoa:ns-size

The result can be a vector.

cocoa:ns-range

The result can be a cons.

objc-bool

The result can be nil or t.

objc-object-pointer

The result can be a string or an array. An autoreleased NSString or NSArray is allocated.

objc-class

The result can be a string naming a class.

1.4.3.2 Defining a method that returns a structure

When a the return type of a method is a structure type such as cocoa:ns-rect then the conversion specified in Special result conversion for define-objc-method can be used. Alternatively, and for any other structure defined with define-objc-struct, the method can specify a variable as its result-style. This variable is bound to a pointer to a foreign structure of the appropriate type and the method should set the slots in this structure to specify the result. For example, the following definitions show a method that returns a structure:

(define-objc-struct (pair
                     (:foreign-name "_Pair"))
  (:first :float)
  (:second :float))
 
(define-objc-method ("pair" (:struct pair) result-pair)
    ((this my-object))
  (setf (fli:foreign-slot-value result-pair :first) 1f0
        (fli:foreign-slot-value result-pair :second) 2f0))

1.4.4 How inheritance works

1.4.2 Defining an Objective-C class introduced the define-objc-class macro with the :objc-class-name class option for naming the Objective-C class. Since this macro is like cl:defclass, it can specify any number of superclasses from which the Lisp class will inherit and also provides a way for superclass of the Objective-C class to be chosen:

For example, both of these definitions define an Objective-C class that inherits from MyObject, via my-object in the case of my-special-object and explicitly for my-other-object:

(define-objc-class my-special-object (my-object)
  ()
  (:objc-class-name "MySpecialObject"))
 
(define-objc-class my-other-object ()
  ()
  (:objc-class-name "MyOtherObject")
  (:objc-superclass-name "MyObject"))

The set of methods available for a given Objective-C class consists of those defined on the class itself as well as those inherited from its superclasses.

1.4.5 Invoking methods in the superclass

Within the body of a define-objc-method or define-objc-class-method form, the local macro current-super can be used to obtain a special object which will make invoke call the method in the superclass of the defining class. This is equivalent to using super in Objective-C.

For example, the Objective-C code:

@implementation MySpecialObject
- (unsigned int)areaOfWidth:(unsigned int)width
                height:(unsigned int)height
{
  return 4*[super areaOfWidth:width height:height];
}
@end

could be written as follows in Lisp:

(define-objc-method ("areaOfWidth:height:" (:unsigned :int))
    ((self my-special-object)
     (width (:unsigned :int))
     (height (:unsigned :int)))
  (* 4 (invoke (current-super) "areaOfWidth:height:"
                               width height)))

1.4.6 Abstract classes

An abstract class is a normal Lisp class without an associated Objective-C class. As well as defining named Objective-C classes, define-objc-class can be used to define abstract classes by omitting the :objc-class-name class option.

The main purpose of abstract classes is to simulate multiple inheritance (Objective-C only supports single inheritance): when a Lisp class inherits from an abstract class, all the methods defined in the abstract class become methods in the inheriting class.

For example, the method "size" exists in both the Objective-C classes MyData and MyOtherData because the Lisp classes inherit it from the abstract class my-size-mixin, even though there is no common Objective-C ancestor class:

(define-objc-class my-size-mixin ()
  ())
 
(define-objc-method ("size" (:unsigned :int))
    ((self my-size-mixin))
  42)
 
(define-objc-class my-data (my-size-mixin)
  ()
  (:objc-class-name "MyData"))
 
(define-objc-class my-other-data (my-size-mixin)
  ()
  (:objc-class-name "MyOtherData"))

1.4.7 Instance variables

In a few cases, for instance when using nib files created by Apple's Interface Builder, it is necessary to add Objective-C instance variables to a class. This can be done using the :objc-instance-vars class option to define-objc-class. For example, the following class contains two instance variables, each of which is a pointer to an Objective-C foreign object:

(define-objc-class my-controller ()
  ()
  (:objc-class-name "MyController")
  (:objc-instance-vars
   ("widthField" objc:objc-object-pointer)
   ("heightField" objc:objc-object-pointer)))

Given an instance of my-controller, the instance variables can be accessed using the accessor objc-object-var-value.

1.4.8 Memory management

Objective-C uses reference counting for its memory management, but the associated Lisp objects are managed by the Lisp garbage collector. When an Objective-C object is allocated, the associated Lisp object is recorded in the runtime system and cannot be removed by the garbage collector. When its reference count becomes zero, the object is removed from the runtime system and the generic function objc-object-destroyed is called with the object to allow cleanup methods to be implemented. After this point, the object can be removed by the garbage collector as normal.

1.4.9 Using and declaring formal protocols

Classes defined by define-objc-class can be made to support Objective-C formal protocols by specifying the :objc-protocols class option. All the standard formal protocols from macOS 10.4 are predefined.

Note: It is not possible to define new protocols entirely in Lisp on macOS 10.5 and later, but existing protocols can be declared using the define-objc-protocol macro.


LispWorks Objective-C and Cocoa Interface User Guide and Reference Manual - 01 Dec 2021 19:38:32