The preceding sections covered the use of existing Objective-C classes. This section describes how to implement Objective-C classes in Lisp.
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 type | FLI type descriptor | Class of associated Lisp object |
---|---|---|
| ||
| 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.
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-name
to 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.
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.
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:
Argument type | Special argument behavior |
---|---|
The argument is a vector. | |
The argument is a vector. | |
The argument is a vector. | |
The argument is a cons. | |
The argument is | |
Depending on the Objective-C class, allows automatic conversion to a string or array. | |
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:
Result type | Special result types supported |
---|---|
The result can be a vector. | |
The result can be a vector. | |
The result can be a vector. | |
The result can be a cons. | |
The result can be | |
The result can be a string or an array. An autoreleased | |
The result can be a string naming a class. |
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.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:
:objc-superclass-name
class option can be used to specify the superclass explicitly.NSObject
is used as the superclass.
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.
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)))
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"))
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.
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.
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