Description

Scheme to Objective C bridge.

Author

Zbigniew, Felix.

Version

Usage

(require-extension objc)

Download

objc.egg

Documentation

This egg provides a basic interface to Objective C from Scheme. You can invoke class and instance methods, access instance variables, and define Objective C classes directly in Scheme.

Method invocation

macro: (objc:send RECEIVER KEYWORD1 ARGUMENT1 ...)

Sends the message KEYWORD1 ARGUMENT1 ... to RECEIVER, which is an objc:instance or objc:class object. This follows the normal Objective C syntax, so the call (objc:send myRect setWidth: 15.0 height: 20.0) invokes the method setWidth:height: on myRect, with arguments 15.0 and 20.0. If the method has no arguments, use a symbol instead of a keyword: (objc:send NSScanner alloc).

macro: (objc:send/safe RECEIVER KEYWORD1 ARGUMENT1 ...)

Identical to (objc:send RECEIVER KEYWORD1 ARGUMENT1 ...), but allows callbacks from Objective C into Scheme. Safe calls are required when invoking any Objective C method that is either implemented in Scheme, or could itself invoke a method defined in Scheme.

macro: (objc:send/maybe-safe RECEIVER KEYWORD1 ARGUMENT1 ...)
parameter: (objc:optimize-callbacks) [default: #t]

Identical to objc:send/safe when the object or class (or any superclass) is implemented in Scheme, and to objc:send otherwise. In general, this greatly improves invocation time for pure Objective C classes and objects, has a negligible impact on Scheme classes, and incurs a 10% penalty for Scheme instances. This optimization can be disabled by setting the parameter above to #f—in which case a safe call will always be used.

macro: (@ RECEIVER KEYWORD ARGUMENT ...)

An abbreviation for (objc:send/maybe-safe RECEIVER KEYWORD ARGUMENT ...). The author prefers this form over the older @[...] form, described next.

read-syntax: @[RECEIVER KEYWORD ARGUMENT ...]

Same as objc:send/maybe-safe. If the receiver is prefixed with the unsafe: keyword, then this form expands into a (objc:send ...) expression instead. Prefixing with safe: guarantees an objc:send/safe call.

For enhanced readability, the bridge accepts hyphenated selector keywords and translates them into their Objective C counterparts by uppercasing any character after a hyphen, then removing the hyphens. For example, the following are equivalent:

 (@ NSDictionary dictionary-with-contents-of-file: name)
 (@ NSDictionary dictionaryWithContentsOfFile: name)

Instances

record: objc:instance

A wrapper for an instance of an Objective C class. The object's description method determines how this record is displayed.

procedure: (objc:class-of ID)

Return the class of objc:instance ID, obtained by sending ID a class message. This procedure also accepts a class object, but the result will generally be the same object.

procedure: (objc:instance->pointer OBJ)

Return the raw pointer associated with objc:instance OBJ.

procedure: (objc:pointer->instance ptr)

Create an objc:instance from a raw instance pointer (an id). Implicitly retains the object, releasing it when the objc:instance is finalized.

Strings

procedure: (objc:nsstring STRING)

Constructs a new NSString from STRING. Currently assumes UTF8 encoding.

read-syntax: @"..."

Equivalent to (objc:nsstring "...").

procedure: (objc:nsstring->string STRING)

Converts an NSString into a Scheme string.

Instance variables

procedure: (objc:ivar-ref OBJECT NAME)

Returns the value of OBJECT's instance variable NAME (which should be a string).

Type conversion is performed on the result, based on the ivar's type. When the ivar refers to a Scheme object (i.e., is a #:slot or #:wrapper), the object is returned transparently.

procedure: (objc:ivar-set! OBJECT NAME VALUE)
procedure: (set! (objc:ivar-ref OBJECT NAME) VALUE)

Sets the value of OBJECT's instance variable NAME to VALUE.

Type conversion is performed on VALUE, based on the ivar type. When the ivar is a #:slot or a #:wrapper, VALUE can be any Scheme object.

Reference counts are automatically managed for id types.

macro: (ivar-ref OBJECT NAME)

Shorthand for (objc:ivar-ref OBJECT (symbol->string NAME)).

macro: (ivar-set! OBJECT NAME VALUE)

Shorthand for (objc:ivar-set! OBJECT (symbol->string NAME) VALUE).

read-syntax: @VAR

Intended for use within methods, this expands to (objc:ivar-ref self "VAR").

Use (set! @var value) to set an instance variable.

Examples: 
(objc:ivar-set! p "x" 3) 
(objc:ivar-ref p "x")     ; => 3 
(ivar-ref p x)            ; => 3   
(set! @x (vector 1 2 3))  ; when @x is a slot: 
@x                        ; => #(1 2 3)

Classes

macro: (define-objc-classes NAME ...)

Locates the Objective C classes NAME ... and defines variables holding pointers to the class objects. NAME may be a symbol or a list of the form (VARIABLE CLASSNAME). For example, (define-objc-classes NSTextView NSTask) is equivalent to

(begin
  (define NSTextView (objc:string->class "NSTextView"))
  (define NSTask (objc:string->class "NSTask")))
record: objc:class

A record representing an Objective C class.

name Class name as a string.
method-list
List of instance methods. The format is ((NAME . SIGNATURE) ...), but may change in the future to a list of objc:method records.
class-method-list List of class methods.
super-class Superclass of this class (as an objc:class).
meta-class Metaclass of this class (as an objc:class).
ivar-list List of all instance variables in this class (as objc:raw-ivar records).
ivars List of ivars in this class defined in Scheme (as objc:ivar records).
all-ivars Aggregate list of objc:ivar records from the class hierarchy.

A note on instance variables: the bridge generates objc:ivar records only for ivars defined in Scheme. However, objc:raw-ivar records are available for all instance variables. This API (ivar-list / ivars / all-ivars) is new and subject to change.

procedure: (objc:class->pointer CLASS)

Return the raw pointer associated with objc:class CLASS.

procedure: (objc:pointer->class ptr)

Create a class from a raw class pointer (a Class).

procedure: (objc:string->class STRING)

Look up and return the Objective C class named STRING.

macro: (objc:define-method CLASS RT ARGS . BODY)
macro: (objc:define-class-method CLASS RT ARGS . BODY)

Define an instance or class method in CLASS.

CLASS An objc:class object representing the destination class.
RT The return type of the method.
ARGS
((KEYWORD TYPE VAR-NAME) ...) or SYMBOL
BODY Body of a lambda comprising the method. The parameters visible inside the lambda are self (a class or instance object), sel (the method selector), and the arguments given in ARGS.

Each list in ARGS adds a method argument of type TYPE, visible to the method body as VAR-NAME. As in Objective C, each argument is associated with a KEYWORD and each KEYWORD is combined into a method name. Hyphenated keywords are accepted, just like in method invocations. A bare SYMBOL can be used as the method name (instead of a list) if no arguments are expected.

TYPE should be a short typename, a symbol such as INT.

Within a method body, you can use (@ super ...) to call a class or instance method of the superclass. Note: super is a reserved keyword, not a variable.

Example:

 (objc:define-method Rect VOID ((set-width: DBL my-w)
                                (height:    DBL my-h))
   (ivar-set! self w my-w)
   (ivar-set! self h my-h))

Method removal is not yet implemented, but redefining a method will override the old definition.

macro: (define-objc-class CLASS SUPERCLASS IVARS . METHODS)

Defines CLASS (a symbol) with superclass SUPERCLASS (a symbol), instance variables IVARS, and methods METHODS. The new classname is imported with define-objc-classes.

SUPERCLASS is looked up for you in the runtime, so it need not be imported.

IVARS is a list of the form ((TYPE NAME) ...), where TYPE is a type qualifier and NAME is a symbol representing the new variable name. Each instance of CLASS will have a separate copy of these variables.

METHODS are method definitions of the form (define-[class-]method RT ARGS . BODY), which are equivalent to calling (objc:define-[class]-method CLASS RT ARGS . BODY) using the current CLASS. These methods are defined in the lexical environment of the surrounding define-objc-class expression. As a simple consequence, you can surround the class definition with a let statement and create "static" variables for the class.

You can also use + as an alias for define-class-method and - for define-method. These correspond to Objective C method definition syntax.

Example:

 (define-objc-class MyPoint NSObject ((DBL x)
                                      (DBL y) 
                                      (slot: closure))
   (define-method ID init
     (print "MyPoint init")
     (@ super init))
   (- DBL getX @x)
   (- DBL getY @y)
   (- ID description
      (sprintf "<MyPoint: (~a, ~a)>" @x @y))

   (- VOID ((move-by-x: DBL a) (y: DBL b))
     (set! @x (+ a @x))
     (ivar-set! self y (+ b (ivar-ref self y))))  ;; more wordy

   (- ID ((init-with-x: DBL a) (y: DBL b))
      (let ((p (@ self init)))
        (@ p move-by-x: a y: b)
        (set! @closure (lambda (msg)
                         (cond ((eq? msg 'initial-x) 
                                (print "MyPoint: initial x was " a))
                               ((eq? msg 'initial-y) 
                                (print "MyPoint: initial y was " b)))))
        p)))

 #;1> (define p (@ (@ MyPoint alloc) init-with-x: 3.4 y: 4.5))
 MyPoint init
 #;2> (@ p move-by-x: 2 y: 3)
 #<objc-instance <MyPoint: (5.4, 7.5)>>
 #;3> ((ivar-ref p closure) 'initial-x)
 MyPoint: initial x was 3.4
parameter: (objc:allow-class-redefinition) [default: #t]

If #f, an error will occur when attempting to redefine an existing class with define-objc-class.

If #t, redefinition is allowed and a warning will be printed.

Types

Objective C is a typed language, and the bridge uses these types to decide how to pass values into and out of Objective C. Each type is represented by a specific string and the bridge provides a variable containing each type string. The variable names and their associated Objective C types are listed below.

objc:define-method uses short versions of the types below, with the objc: prefix removed. For example, use ID instead of objc:ID. The full names are generally used for lower-level methods such as objc:add-method and objc:set-ivars!.

objc:ID id
objc:CLASS Class
objc:SEL SEL
objc:INT int
objc:DBL double
objc:FLT float
objc:CHR char
objc:SHT short
objc:LNG long
objc:USHT unsigned short
objc:UINT unsigned int
objc:UCHR unsigned char
objc:ULNG unsigned long
objc:BOOL BOOL
objc:PTR void *
objc:CHARPTR char *
objc:NSRECT NSRect
objc:NSSIZE NSSize
objc:NSPOINT NSPoint
objc:NSRANGE NSRange
objc:VOID void

Instance variables defined in define-objc-class use the type qualifiers in the following table.

DBL, INT, etc.
An objc:DBL, objc:INT, etc. type—use short typenames, as in objc:define-method.
ID
An Objective C instance (objc:ID) with automatic memory management.
#:outlet An Interface Builder outlet (objc:ID). Memory management is not performed.
#:slot Holds a scheme object, such as a vector or closure.
#:wrapper Holds a scheme object like #:slot, but is less efficient.

Type conversions

Numeric types (such as double and int) are converted to and from Scheme numbers just as in Chicken's C FFI.

Class types are wrapped in unique objc:class records when passed to Scheme—see Class Proxies for more details.

An id, or instance, type is almost always represented as an objc:instance record, which is a thin wrapper around a pointer to the Objective C object. There is generally no automatic conversion to or from Scheme objects, even when the object has a reasonable direct representation in Scheme. For example, you may not pass the number "3" to a method expecting an id, even though "3" could be represented as an NSNumber. Conversely, an NSNumber representing "3" remains an objc:instance when returned to Scheme.

There is one exception to this rule: you may pass a string to any method which expects an id, and it will become an NSString. Of course, you can always perform conversions manually using Objective C methods—continuing the example above, an (@ NSNumber number-with-int: 3) can indeed be passed as an id argument. The author has written convenience functions for NSNumber, NSDictionary, and NSArray which reduce the drudgery of conversion. These should be available soon; check here for updates.

Objective C lacks a boolean type; booleans are char types where zero is false and non-zero is true. Since (char)0 is rare, we convert it to #f when passed to Scheme, which allows Scheme predicates to work without a special test. We also transform #t and #f to (char)1 and (char)0 when passed to Objective C. Other character values are passed through as-is. Unsigned char values, on the other hand, never represent booleans and aren't transformed.

Selectors are converted to strings when passed to Scheme, and strings converted back to selectors when passed to Objective C. Note that selectors may be wrapped in objc:selector objects in the future.

CHARPTR (char *) types are converted to Scheme strings, but conversion to CHARPTR is disabled.

NSRect, NSPoint, NSSize and NSRange structures can be sent to and received from Objective C. Each is represented by a record on the Scheme side.

record: ns:rect

ns:make-rect is provided as an alias for the default constructor make-ns:rect. The same is true for the other records.

x X coordinate of origin (NSRect.origin.x)
y Y coordinate of origin (NSRect.origin.y)
width Width of rectangle (NSRect.size.width)
height Height of rectangle (NSRect.size.height)
record: ns:size
width Width
height Height
record: ns:point
x X coordinate
y Y coordinate
record: ns:range
location Start index, 0-based
length Length of range

Other than the four exceptions above, struct, union, and array types cannot be sent to or received from Objective C.

Memory management

The bridge strives to handle memory management automatically. In general terms, this is accomplished by implicitly retaining an instance object when it is passed into Scheme, and releasing it when the objc:instance is finalized. The bridge knows that certain selectors, such as alloc and copy, by convention donate a retain reference to you, and adjusts its retain count accordingly. Furthermore, passing an objc:instance into Objective C retains and autoreleases that object, which is necessary to ensure that short-lived objects (such as automatically created strings) remain valid until the end of a method invocation. (Each invocation is wrapped in an autorelease pool, hence the autorelease.)

In general, this means you don't have to worry about releasing, autoreleasing or retaining objects. The expression (let ((m [@ MyPoint alloc])) (void)), for example, incurs no memory penalty as m is garbage collected like any other Scheme object. This principle extends to NSStrings that are created by the bridge when converted from Scheme strings. Additionally, you may return newly allocated objects from Scheme classes without autoreleasing them.

Instance variables which hold Objective C objects—id types—are also managed automatically. Those defined in a Scheme class are properly retained when using objc:ivar-set!, and properly released in dealloc. You should use the #:outlet type qualifier for Interface Builder outlets, which will turn off automatic management as IB expects. Ivars defined in a pure Objective C class are never retained or released automatically, as these classes expect to manage memory themselves. If you must access such a variable directly, you must manually send it retain and release messages (editor's note: the API for such is not exposed right now).

There are some limitations. Overriding memory-management selectors such as alloc, release, and dealloc is not supported (although alloc does appear to work fine, caveat emptor). Sending an autorelease message has no effect due to the current implementation of method invocation. Finally, although the author has tried to ensure automatic memory management works as advertised, certain cases (especially involving calls from Objective C to Scheme) have not been tested and may be problematic at this point.

Class proxies

In order to implement ivar memory management and transparent access to Scheme objects stored in ivars, the bridge needs to maintain metadata for each class defined in Scheme. Enter the class proxy, a unique objc:class record for each class. Whenever a class pointer is passed into Scheme, this corresponding proxy is looked up and returned. This means that Scheme can store information about classes beyond that available in the Objective C runtime.

For pure Objective C classes, this proxy is generic, and springs into being dynamically the first time the class is referenced. It doesn't contain any extra data; it simply notes the class is pure Objective C.

For classes defined in Scheme, the proxy is created at class definition time, and contains (amongst other things) extended instance variable data, including type qualifiers such as slot: and outlet:. objc:ivar-set! will look up the object's class, find the appropriate ivar and notice any type qualifier.

Instance proxies per se are not (yet) implemented. Although objects are wrapped in objc:instance records, each is merely a non-unique wrapper around a pointer and contains no extra data. Of course, since instance variables can hold Scheme objects, you can store as many "proxy" objects as you like inside any instance. On the other hand, you have to manually access this data via ivar-ref or by sending the Objective C object a message, which can be cumbersome. In practice, it may be useful to do this transparently, having an interchangeable proxy Scheme object and Objective C instance. This is an active area of research.

Lowlevel

procedure: (objc:register-class CLASSNAME SUPERCLASS)

Registers CLASSNAME (a string) having superclass SUPERCLASS (an objc:class) with the Objective C runtime. An error is raised if CLASSNAME already exists.

procedure: (objc:set-ivars! CLASS IVARS)

Defines in CLASS (an objc:class) the instance variables in IVARS (a list of objc:instance-var records). The offset parameter of the instance variable records is ignored.

Warning: all old instance variables in MyClass will be removed first. Also, we don't check for conflicts with superclass instance variables. This should be remedied in a future release.

Example: 
(objc:set-ivars! MyClass (list (make-objc:raw-ivar "jimmy" objc:INT 0) 
                               (make-objc:raw-ivar "cammy" objc:DBL 0)
procedure: (objc:add-method CLASS METHOD TYPES PROC)
procedure: (objc:add-class-method CLASS METHOD TYPES PROC)

Adds a class or instance method to CLASS.

METHOD
Method name as a string (e.g. "setWidth:height:")
TYPES
List of encoded argument type strings (such as objc:INT or "@").
PROC Scheme procedure representing the method.

The structure of the TYPES list is (RETURN-TYPE SELF SELECTOR METHOD-ARGS...).

Transformation: 
(objc:define-method MyClass DBL ((sel1: INT i) (sel2: DBL d)) 
                                (print i) (+ i d)) 
=> 
(objc:add-method MyClass "sel1:sel2:" (list objc:DBL objc:ID objc:SEL objc:INT objc:DBL) 
                                      (λ (self sel i d) (print i) (+ i d))) 
procedure: (objc:wrap X)
procedure: (objc:unwrap X)

Wrap or unwrap the Scheme object X inside an Objective C instance (specifically, a Scheme_Object_Wrapper) so that it can be passed as an id type. Essentially, these functions allow you to tunnel a Scheme object through the Objective C bridge, when both endpoints are written in Scheme. At the moment, the resulting object cannot be accessed meaningfully from the Objective C side.

These functions are also used to implement the #:wrapper type qualifier for instance variables.

record: objc:raw-ivar

A record describing an Objective C instance variable as seen from the Objective C (as opposed to Scheme) side. Returned by objc:class-ivar-list, and used by objc:set-ivars!.

name Name as a string.
type Type as an encoded type string.
offset Offset within class -- for debugging only.
record: objc:ivar

A record describing Scheme's view of an Objective C instance variable. At the moment, fields include those of objc:raw-ivar with the following addition:

function Keyword #:slot, #:wrapper, #:outlet, or #:ivar.
procedure: (with-autorelease-pool THUNK)

Creates an autorelease pool that lasts for the duration of the thunk.

A global autorelease pool is created automatically at startup, one is wrapped around the main Application Kit event loop, one is wrapped around every call to Objective C, and memory management is generally otherwise automatic. It is considered unlikely you will have to use this, unless you send Objective C messages directly inside a foreign-lambda.

procedure: (objc:import-classes-at-toplevel!)

Import every class visible to the runtime, as if define-objc-classes had been run on all available classes. Useful for debugging. Note that some (rare) classes are not derived from NSObject, and will not respond to standard NSObject selectors, may throw exceptions, or may crash if used.

procedure: (objc:get-class-list)

Looks up and returns a list of all available classes. At startup, the result of this call is stored in the variable objc:classes.

Cocoa

(require-extension cocoa)

It is possible to create Cocoa applications using this extension. See Creating a Cocoa Application in Chicken for a document which walks you through implementing Apple's famous Currency Converter application.

Also, a working application is included in this egg. Untar the egg, change to the tests/ directory, and type make. An application called Temperature Converter will be built.

procedure: (ns:application-main)

Starts the main event loop of a Cocoa-based application. If any arguments are present on the command line, they will be passed to NSApplicationMain.

procedure: (ns:beep)

Plays the default system sound using NSBeep.

procedure: (ns:log FORMAT-STR ARGS...)

Logs a message using NSLog. The optional ARGS are interpolated into FORMAT-STR as in printf.

procedure: (ns:rect-fill RECT)

Fills the passed RECT (an ns:rect) with the current color, using NSRectFill.

procedure: (cocoa:run)

Send the run message to the global NSApplication object. This is intended to be used during debugging, to restart the main application event loop after an error has returned you to the REPL.

Some global Application Kit functions, such as NSRectFill, take structures which are passed by value. However, the Chicken FFI only supports passing structs by pointers. A workaround is provided in the egg; if you need to wrap such a function, see ns:rect-fill in cocoa.scm for an example.

License

Copyright (c) 2005, J. "Zb" Ursetto.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer. Redistributions in
  binary form must reproduce the above copyright notice, this list of
  conditions and the following disclaimer in the documentation and/or
  other materials provided with the distribution. Neither the name of the
  author nor the names of its contributors may be used to endorse or
  promote products derived from this software without specific prior
  written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.