Copyright ©1999 by AAA+ Software Forschungs- und Entwicklungs Ges.m.b.H.  All Rights Reserved.

3 Using Joy as an Objective-C Interpreter





In the previous section you used the Joy interpreter to send messages to Objective-C objects. But of course that's not the whole story!

The Joy 2.0 scripting language is based on ECMA JavaScript, and extends it with features of C and Objective-C. The resulting language is essentially a superset of both JavaScript and Objective-C. This means you can copy Objective-C code from ProjectBuilder into the Joy command window and have it interpreted. It also means you can develop new code in Joy, taking advantage of its prototyping capabilities, and migrate it (or just the most performance-critical parts) to Objective-C when you are done.

Joy 1.x used Tcl as the base language. The Tcl dialect of Joy is still available. You can switch languages by using the Preferences menu item in the Info submenu of InterfaceBuilder's Joy menu.

This tutorial assumes you are using JavaScript, which is the default.

Creating and Freeing Objective-C Objects

First open a Joy command window by choosing Command Window... in InterfaceBuilder's Joy menu. Then type

    js> o = [[NSObject alloc] init]

You will get output similar to the following:

    (NSObject *)0x1ad258

This is the string representation of the Objective-C object you just created. You will notice it looks like an Objective-C type cast. This is so you can copy the output and paste it back into the interpreter as a command. (Try it!) Joy supports all Objective-C operators, including type casts. You might wonder what happens if you cast some random number to an Objective-C object. Will Joy crash?

    js> (NSObject *)42
    ConversionError: Objective-C type cast failed

No! Because Joy internally maintains a table of addresses that correspond to valid Objective-C objects. This guards against a whole class of runtime errors: References to freed objects. Let us free our object:

    js> [o release]

And send it a message:

    js> [o description]
    ReferenceError: reference to freed Objective-C id

Now that we are rid of the boring NSObject, let's create a new object of a more interesting class, say NSString:

    js> string = [NSString stringWithCString: "Joy"]
    Joy

And ask it for the count of characters in the string:

    js> [string length]
    ReferenceError: reference to freed Objective-C id

Oops! What happened? Well, the stringWithCString: class method (like most methods that allocate a new object and return it to the caller), autorelease's the string before it is returned. The application's autorelease pool is emptied after every event it processes, so the string object ceased to exist in the moment you hit the return key. Now try the following:

    js> string = [[NSString stringWithCString: "Joy"] retain]
    Joy
    js> [string length]
    3
    js> [string release]

This problem appears only when you are interactively typing Joy commands into a command window. In any longer stretch of code, where there is no danger of the current autorelease pool being emptied, you do not need to retain/release your objects. You might want to consult the Yellow Box documentation if you are unsure how its reference counting scheme works.

JavaScript uses automatic garbage collection for its objects. A JavaScript object will be freed automatically when it is not referenced any more. Joy allows you to apply the same mechanism to Objective-C objects. Try the following:

    js> string = [new NSString initWithCString: "Joy"]
    Joy
    js> [string length]
    3
    js> string = nil
    null

Teaching an Old Object a New Trick

Joy allows you to extend any Objective-C class by methods written in Joy's scripting language. These methods can be called transparently from Objective-C. Since Joy is interpreted, you can implement or override methods at runtime, and even for individual objects, not just classes!

So let's create a new object. Just for the fun of it we will use Java syntax this time:

    js> o = NSObject.alloc().init()
    (NSObject *)0x747db8

And then implement an Objective-C method just for that one object:

    js> o.dance = function() {
        return @"I can't dance!"
    }
    
    - (id)dance {
        return @"I can't dance!";
    }

We implemented a new Objective-C method just by assigning a JavaScript function to some property of an Objective-C object. The assignment expression's return value is the new Objective-C method. Because there was no previous implementation of the selector dance, Joy assumed id as the method return type by default.

You can now call that method from both JavaScript and Objective-C. We can easily simulate a call from Objective-C by using the performSelector: method:

    js> [o dance]
    I can't dance!
    js> [o performSelector: @selector(dance)]
    I can't dance!

You can verify that o is really the only object that responds to the new selector:

js> [new NSObject dance]
    Objective-C NSInvalidArgumentException: *** -[NSObject dance]: selector not recognized

Assigning a function to a property is the JavaScript way of implementing methods. Because the Joy language is a superset of Objective-C, there is of course also an Objective-C way (type it in):

    js> @implementation o (Dance)
    - (NSString *)dance {
        return @"I *
can* dance!"
    }
    @end

This is the same syntax you use to implement a category in Objective-C, but Joy allows you to add a category to either a class or an object, while Objective-C can do it only for classes. The advantage of this syntax over the JavaScript one is that you can specify arbitrary return and argument types. The process of adding method implementations at runtime has traditionally been called teaching in Joy, so you can use @teach as a synonym for @implementation. (There is a difference in that the argument of @teach can be any expression that evaluates to a class or object, while @implementation expects just a class or variable name.)

Joy can not only teach new methods at runtime, it can also do the reverse:

    js> [o dance]
    I *can* dance!
    js> ObjC.unteach(o,"dance")
    1
    js> [o dance]
    I can't dance!

The old implementation of a selector is not lost when you override it, but reappears when you unteach the overriding implementation. You can even call the old implementation of any selector from the new one, by using the special message target former:

    js> @teach o
    - dance {
        return [former dance] + " " + [former dance]
    }
    @end

    js> [o dance]
    I can't dance! I can't dance!

Of course the former implementation can be in Objective-C. This feature turns every Objective-C method in the Yellow Box frameworks into a "hook" which you can patch with scripted code of your own! (A real joy for hackers... But also very useful for debugging and experimenting.)

If you get confused about what is the most current implementation of a selector, you can always type:

    js> print(o.dance)
    
    - (id)dance {
        return [former dance] + " " + [former dance];
    }

The output you get is not simply the code you typed in (which was discarded after being compiled to JavaScript byte code), but the result of decompiling the byte code again! If the method is implemented in Objective-C, you can still view at least its prototype:

    js> print(o["performSelector:"])
    
    
- (id)performSelector:(SEL)arg0;

This example is interesting because it shows what to do when a selector contains colons. JavaScript identfiers may contain only letters, numbers, and underscores, so writing just o.performSelector: would result in a syntax error. (Try it!) In a case like this you have to quote the selector and access the property as an array element instead of using the normal dot notation.

Calling C Functions

Not everything is an object. Even in the extremely object oriented Yellow Box API some functionality is still packaged as plain C functions. Joy allows you to use them:

    js> NSRunAlertPanel("Hi!", "What do you think of Joy so far?",
                        "Cool!", "Wow!", "I'm impressed!")

Like for Objective-C methods, you can view any function's prototype, just by typing:

    js> print(NSRunAlertPanel)
    
    int NSRunAlertPanel(id title, id msg, id defaultButton,
                        id alternateButton, id otherButton);

If you are a C afficionado, try out a few functions from the C library. Chances are that Joy will know about them. You might wonder how this works. Is there a hard-coded table of C function prototypes hidden somewhere? No, much better:

Parsing Objective-C Header Files

Because the Joy language is compatible with Objective-C it is possible to parse complete Objective-C header files, declarations, prototypes, everything, as Joy source code! Try this:

    js> #import <AppKit/AppKit.h>

Nothing happens. Why? Because AppKit.h has already been imported! Joy includes a feature to parse a header file just once and generate a precompiled file from it that can be sourced efficiently. One such precompiled file (containing AppKit.h, among others) is imported automatically when Joy starts up. If you like, you can try importing one of your own Objective-C header files now.

Joy can not only make use of the function prototypes in header files, it also understands variable declarations:

    js> NSApp
    (IB *)0x110a58

And preprocessor macros:

    js> NSAlertDefaultReturn
    1

Typedefs:

    js> @encode(NSRange)
    {_NSRange="location"I"length"I}

Even inline functions:

    js> NSMakeRange(1,2)
    {location:1, length:2}
    js> print(NSMakeRange)

The last example also shows how elegantly Joy 2.0 handles C structs.

Using Conditional Compilation

The #import which you used above might have started you wondering if Joy uses a preprocessor, like C.  Technically, the answer is no, but most features of the C preprocessor, such as macro constants and conditional compilation, have been built into the Joy parser.

This is useful for ensuring source code compatibility between Joy and Objective-C. While Joy was designed to be as compatible with Objective-C as possible, there are still limitations. (Chances are that inline assembler code will never work in Joy...) If you hit one of those, you can always use

    js> #ifdef __JOY__
    ... // Source code that should be seen only by Joy
    #else
    ... // Objective-C source code
    #endif

The next section explains how you can use Joy to access Java.