Cocoa Programming in OpenMCL


Table of Contents
Overview.
General.
Some steps in the right direction.
The application kit and native threads.
Writing (and reading) Cocoa code
@class [Macro]
@selector [Macro]
def-objc-class [Macro]
define-objc-method [Macro]
define-objc-class-method [Macro]
Method-definition syntax examples.
Coercion of method arguments and result types.
Method redefinition constraints.

Overview.

For the last year or so, OpenMCL source distributions have been released with examples that showed that it was possible to access the Cocoa application framework via OpenMCL's foreign function interface. On the one hand, these examples seemed to show off the power of Cocoa: it was apparently possible to create a working (if rudimentary) lisp development environment with a relatively small amount of lisp code. On the other hand, the examples seemed to demonstrate that a lot of infrastructure was missing:


General.

This document assumes some general familarity with Cocoa and with the Objective C programming language and its runtime conventions.

OpenMCL is ordinarily a command-line application (it doesn't have a connection to the OSX Window server, doesn't have its own menubar or dock icon, etc.) By opening some libraries and jumping through some hoops, it's able to to sort of transform itself into a full-fledged GUI application (while retaining its original TTY-based listener.) The general idea is that this hybrid environment can be used to test and protoype UI ideas and the resulting application can eventually be fully transformed into a bundled, double-clickable application. This is to some degree possible, but there needs to be a bit more infrastructure in place before many people would find it easy.

As it was originally built, the demo Cocoa IDE used .nib files created by Apple's Interface Builder to describe the layout and structure of its windows and menus. Integration with IB is certainly a good thing (IB is very good at what it does), but it's certainly possible to create and modify windows and menus dynamically. There should ideally be some (more) examples that show how to do this in OpenMCL.

Cocoa application use the NSLog function to write informational/warning/error messages to the application's standard output stream. A GUI application's standard output is diverted to a logging facility that can be monitored with the Console application (found in /Applications/Utilities/Console.app); in the hybrid environment, the application's standard output stream is usually the initial listener's standard output stream. With two different buffered stream mechanisms trying to write to the same underlying Unix file descriptor, it's not uncommon to see NSLog output mixed with lisp output on the initial listener.


Some steps in the right direction.

OpenMCL 0.14's native threads make it practical to address some aspects of the first two of these concerns; Randall Beer has developed and contributed a “Cocoa Bridge”, which provides many features to make Cocoa programming in OpenMCL more robust and convenient. The bridge is documented in greater detail in “ccl:examples;CocoaBridgeDoc.txt”; the OpenMCL Cocoa examples have been rewritten to use the features offered by the bridge.


The application kit and native threads.

AppKit (the parts of Cocoa that deal with events, window management, drawing, and other aspects) really wants all event handling, GUI object creation, and drawing to take place on a distinguished thread. Apple has published some guidelines that discuss these issues in some detail (see here, for instance) but there can sometimes be unexpected behavior when objects are created in threads other than the distinguished event thread (e.g., the event thread sometimes starts performing operations on objects that aren't fully initialized.) It's certainly more convenient to do certain types of exploratory programming by typing things into a listener or evaluating a “defun” in an Emacs buffer; it may sometimes be necessary to be aware of this issue while doing so.

Each thread in the Cocoa runtime system is expected to maintain a current “autorelease pool” (an instance of the NSAutoreleasePool class); newly created objects are often added to the current autorelease pool (via the -autorelease method), and periodically the current autorelease pool is sent a “-release” message, which causes it to send “-release” messages to all of the objects that've been added to it.

If the current thread doesn't have a current autorelease pool, the attempt to autorelease any object will result in a severe-looking warning being written via NSLog. The event thread maintains an autorelease pool (it releases the current pool after each event is processed and creates a new one for the next event), so code that only runs in that thread should never provoke any of these severe-looking NSLog messages.

To try to suppress these message (and still participate in the Cocoa memory management scheme), each listener thread (the initial listener and any created via the “New Listener” command in the IDE) is given a default autorelease pool; there are REPL colon-commands for manipulating the current listener's “toplevel auturelease pool”.

In the current scheme, every time that Cocoa calls lisp code, a lisp error handler is established which maps any lisp conditions to ObjC exceptions and arranges that this exception is raised when the callback to lisp returns. Whenever lisp code invokes a Cocoa method, it does so with an ObjC exception handler in place; this handler maps ObjC exceptions to lisp conditions and signals those conditions.

Any unhandled lisp error or ObjC exception that occurs during the execution of the distinguished event thread's event loop causes a message to be NSLog'ed and the event loop to (try to) continue execution. Any error that occurs in other threads is handled at the point of the outermost Cocoa method invocation. (Note that the error is not necessarily “handled” in the dynamic context in which it occurs.)

Both of these behaviors could possibly be improved; both of them seem to be substantial improvements over previous behaviors (where, for instance, a misspelled message name typically terminated the application.)


Writing (and reading) Cocoa code

The syntax of the constructs used to define Cocoa classes and methods has changed a bit (it was never documented outside of the source code and never too well documented at all), largely as the result of functionality offered by Randall Beer's bridge; the “standard name-mapping conventions” referenced below are described in his CocoaBridgeDoc.txt file, as are the constructs used to invoke (“send messages to”) Cocoa methods.

All of the symbols described below are currently internal to the CCL package.


@class [Macro]

Syntax

@class class-name

Description

Used to refer to a known ObjC class by name. (Via the use LOAD-TIME-VALUE, the results of a class-name -> class lookup are cached.)

Arguments

 

class-name

a string which denotes an existing class name, or a symbol which can be mapped to such a string via the standard name-mapping conventions for class names


@selector [Macro]

Syntax

@selector string

Description

Used to refer to an ObjC method selector (method name). Uses LOAD-TIME-VALUE to cache the result of a string -> selector lookup.

Arguments

 

string

a string constant, used to canonically refer to an ObjC method selector


def-objc-class [Macro]

Syntax

def-objc-class class-name superclass-name &rest slot-specs

Description

Defines class-name to be an ObjC class whose superclass is the class named by superclass-name and whose instance variables are those described by slot-specs. ObjC doesn't generally allow class redefinition and doesn't allow classes to be forward-refrerenced, so superclass-name should be the name of a defined ObjC class (and class-name should not be) when this is executed.

Arguments

 

class-name

A string which denotes a new ObjC class name, or a lisp symbol which can be mapped to such a string via the standard name-mapping conventions for class names

superclass-name

A string which denotes an existing ObjC class name, or a lisp symbol which can be mapped to such a string via the standard name-mapping conventions for class names

slot-specs

a list of 0 or more instance variable specifiers, each of which can be of one of the following forms:

  • a symbol or string which denotes an instance variable of foreign type :ID (which means roughly “pointer to ObjC instance or class”); symbols in this context are mapped to lowercase strings

  • a list whose CAR is such a symbol or string and whose CADR is a foreign type specifer.

  • a list whose CADR is a foreign type specifier and whose CAR is a list whose car is a symbol and whose CADR is a string; this allows some lisp constructs to refer to the instance variable via a symbol which might contain characters (hyphens, asterisks) that aren't legal components of an ObjC variable name.


define-objc-method [Macro]

Syntax

define-objc-method (selector class-name) &body body

Description

Defines an ObjC-callable method which implements the specified message selector for instances of the existing ObjC class class-name.

Arguments

 

selector

either a string which represents the name of the selector or a list which describ+es the method's return type, selector components, and argument types (see below.) If the first form is used, then the first form in the body must be a list which describes the selector's argument types and return value type, as per DEFCALLBACK.

class-name

either a string which names an existing ObjC class name or a list symbol which can map to such a string via the standard name-mapping conventions for class names.


define-objc-class-method [Macro]

Syntax

define-objc-class-method (selector class-name) &body body

Description

Like DEFINE-OBJC-METHOD, only used to define methods on the class named by class-name and on its subclasses.

Arguments

As per DEFINE-OBJC-METHOD

For both DEFINE-OBJC-METHOD and DEFINE-OBJC-CLASS-METHOD, the “selector” argument can be a list whose first element is a foreign type specifier for the method's return value type and whose subsequent elements are either:


Method-definition syntax examples.

The following 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)
  

Coercion of method arguments and result types.

The method definition macros coerce method arguments of foreign type :<BOOL> to T/NIL (this is done via an assignment at the beginning of the body.) If the method's declared result type is :<BOOL>, a non-nil, non-0 value returned by the body is coerced to 1 (#$YES) and NIL or 0 is returned as 0 (#$NO).


Method redefinition constraints.

Objective C methods can be redefined at runtime, but their signatures (the foreign result type and the types of foreign arguments) shouldn't change. If a method shadows a method defined in a superclass, it should have the same type signature as the superclass's method (though it's legal for methods defined in disjoint classes to have different signatures.)

Note that adding or removing arguments has the effect of changing the selector used to name and invoke the method.