Outdated egg!
This is an egg for CHICKEN 4, the unsupported old release. You're almost certainly looking for the CHICKEN 5 version of this egg, if it exists.
If it does not exist, there may be equivalent functionality provided by another egg; have a look at the egg index. Otherwise, please consider porting this egg to the current version of CHICKEN.
Objc
This is version 0.5 of the objc extension library for Chicken Scheme.
TOC »
Description
Scheme to Objective C bridge
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.
This egg requires Mac OS X 10.5 or earlier, and libffi.
Method invocation
- (objc:send RECEIVER KEYWORD1 ARGUMENT1 ...)syntax
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).
- (objc:send/safe RECEIVER KEYWORD1 ARGUMENT1 ...)syntax
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.
- (objc:send/maybe-safe RECEIVER KEYWORD1 ARGUMENT1 ...)syntax
- objc:optimize-callbacksparameter
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 objc:optimize-callbacks to #f---in which case a safe call will always be used.
- (@ RECEIVER KEYWORD ARGUMENT ...)syntax
An abbreviation for (objc:send/maybe-safe RECEIVER KEYWORD ARGUMENT ...). The older @[...] form is deprecated.
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)
- @[RECEIVER KEYWORD ARGUMENT ...]read
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.
This form is deprecated in favor of (@ ...).
Instances
- objc:instancerecord
A wrapper for an instance of an Objective C class. The object's description method determines how this record is displayed.
- objc:class-of IDprocedure
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.
- objc:instance->pointer OBJprocedure
Return the raw pointer associated with objc:instance OBJ.
- objc:pointer->instance ptrprocedure
Create an objc:instance from a raw instance pointer (an id). Implicitly retains the object, releasing it when the objc:instance is finalized.
Strings
- objc:nsstring STRINGprocedure
Constructs a new NSString from STRING. Currently assumes UTF8 encoding.
- @"..."read
Equivalent to (objc:nsstring "...").
- objc:nsstring->string STRINGprocedure
Converts an NSString into a Scheme string.
Instance variables
- objc:ivar-ref OBJECT NAMEprocedure
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.
- objc:ivar-set! OBJECT NAME VALUEprocedure
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.
A SRFI-17 setter is also provided, in the form
(set! (objc:ivar-ref OBJECT NAME) VALUE)
- (ivar-ref OBJECT NAME)syntax
Shorthand for (objc:ivar-ref OBJECT (symbol->string NAME)).
- (ivar-set! OBJECT NAME VALUE)syntax
Shorthand for (objc:ivar-set! OBJECT (symbol->string NAME) VALUE).
- @VARread
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
- (define-objc-classes NAME ...)syntax
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")))
- objc:classrecord
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.
- objc:class->pointer CLASSprocedure
Return the raw pointer associated with objc:class CLASS.
- objc:pointer->class ptrprocedure
Create a class from a raw class pointer (a Class).
- objc:string->class STRINGprocedure
Look up and return the Objective C class named STRING.
- (objc:define-method CLASS RT ARGS . BODY)syntax
- (objc:define-class-method CLASS RT ARGS . BODY)syntax
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.
- (define-objc-class CLASS SUPERCLASS IVARS . METHODS)syntax
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
- objc:allow-class-redefinitionparameter
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.
- ns:rectrecord
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)
- ns:sizerecord
width Width height Height
- ns:pointrecord
x X coordinate y Y coordinate
- ns:rangerecord
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. Class proxies also keep track of which classes have Scheme implementations, used for minimizing safe callbacks.
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
- objc:register-class CLASSNAME SUPERCLASSprocedure
Registers CLASSNAME (a string) having superclass SUPERCLASS (an objc:class) with the Objective C runtime. An error is raised if CLASSNAME already exists.
- objc:set-ivars! CLASS IVARSprocedure
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)
- objc:add-method CLASS METHOD TYPES PROCprocedure
- objc:add-class-method CLASS METHOD TYPES PROCprocedure
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...).
You may add Scheme methods to pure Objective C classes; when class proxies are enabled, this will automatically taint the class so that safe calls are used. Currently, a warning is printed when a class is tainted.
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) (lambda (self sel i d) (print i) (+ i d)))
- objc:wrap Xprocedure
- objc:unwrap Xprocedure
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.
- objc:raw-ivarrecord
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.
- objc:ivarrecord
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.
- with-autorelease-pool THUNKprocedure
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.
- objc:import-classes-at-toplevel!procedure
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.
- objc:get-class-listprocedure
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.
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.
- ns:application-mainprocedure
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.
- ns:beepprocedure
Plays the default system sound using NSBeep.
- ns:log FORMAT-STR ARGS...procedure
Logs a message using NSLog. The optional ARGS are interpolated into FORMAT-STR as in printf.
- ns:rect-fill RECTprocedure
Fills the passed RECT (an ns:rect) with the current color, using NSRectFill.
- cocoa:runprocedure
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.
About this egg
Requirements
Mac OS X Intel or PPC (10.5 or earlier). This egg does not run on GNUStep. It also does not run on OS X 10.6 or later due to significant internal changes in Objective C, and there are no plans to make it compatible.
libffi. Chicken itself need not be compiled with libffi support; libffi just needs to be installed on your system. Install one of these:
- On 10.5, libffi should already be installed. The egg will try to use the system copy.
- libffi-20071207: Preferred build, from a recent SVN snapshot.
- libffi-3 (Felix's build). Tried and true, but may not work on Leopard.
- libffi 2.1: last public release of libffi, several years old. Probably unusable.
Author
Version history
- 0.5.0
- Port to Chicken 4, with assistance from Alex Shinn.
- 0.4.3
- Callback-safe context for release; add-method taints pure ObjC classes. Verify egg on x86.
- 0.4
- Class proxies, ivar refcounting, ivar Scheme objects, objc:wrap/unwrap, (@ ...) syntax, hyphenated selectors, callback optimization. Detailed changelog.
- 0.3
- Enable struct support; change to ns: prefix.
- 0.2
- Add objc:class-of and rename extension
- 0.1
- Initial release
License
Copyright (c) 2005, 2006, 2007, 2008, 2009, 2010 Jim 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.