Using Scripting Languages for
Cocoa Development

With Leopard, Mac OS X has raised the bar for using scripting languages as full application development tools. Leopard ships with full support for the RubyCocoa Bridge, and the PyObjC bridge. These two bridges give developers full access not only to system APIs, but to the Cocoa frameworks like AppKit and Core Data, allowing you to build fully native Mac OS X applications in Ruby or Python. The RubyCocoa and PyObjC bridges allow you to freely mix code written in Objective-C with code written in the scripting language. This allows you to quickly build prototypes and then optimize by implementing performance-critical pieces in Objective-C.

Why Use Scripting Languages?

In previous versions of Mac OS X, scripting languages already bridged to system layers, by providing BSD and BSD-like functionality. Many scripting languages provide some support for building GUI applications with X11 wrappers. A drawback of this approach is that applications developed with X11 wrappers do not have a native Mac OS X look and feel. Such applications might lack certain features that users expect, or they might have default menu layouts or controls that don't conform to the Mac OS X interface guidelines.

Both Ruby and Python are excellent languages for application development. Both the RubyCocoa bridge and the PyObjC bridge have been actively developed for many years, so they are mature and stable. Both offer strong, consistent object-oriented programming models, and Python also lets you program in a procedural or functional style if you prefer. Ruby and Python are both first class citizens for application development on Leopard. Both languages have project templates and can take full advantage of Xcode 3.

The applications you create with Ruby and Python are packaged exactly like native Mac OS X applications. Your end-users will not be able to tell the difference. What's more, Apple is committed to binary compatibility between releases of Mac OS X. This means you will no longer need to embed the runtime and language interpreter in your application.

Crossing the Bridge: Special Language Features

One of the hurdles to building scripting language bridges is dealing with disparate features between languages. The first such example is that of colons in method names. In Objective-C, the colons that separate parameters are part of the method name (including the trailing colon). As an example, consider the NSString method stringWithCharacters:length:. Neither Ruby nor Python allow colons in method names. So a consistent mapping must be used in order to call Objective-C methods from the scripting language.

The method adopted for the bridges simply substitutes a '_' character for every ':' character, including the trailing ': character. So, the stringWithCharacters:length: method becomes stringWithCharacters_length_. This simple method name mapping is consistent, and unambiguous. There are no exceptions or special cases allowed.

Another hurdle comes about from method signatures. In scripting languages, arguments are generally typeless, whereas in Objective-C, arguments do have explicit types. The scripting language bridges must try to derive type information for newly declared methods from context. This means that if a method is overridden from a superclass (you can freely derive new classes in the scripting language from classes implemented in Objective-C), the bridge determines type information from the method in the superclass. Similarly, if a method is declared in an Objective-C protocol, or if it is a delegate method, the bridge gets type information that way.

But it isn't always possible to get type information this way. †For those cases where context is unavaiable, the scripting language may provide hints to the bridge using language specific features. †In Python, @ decorators are used. †Ruby offers attribute accessors. †For example, a Python class may denote a class method by adding the @classmethod decorator. † The PyObjC bridge adds @typedAccessor to add addition type information to a method. In the next Python example, the decorators @typedAccessor, and @classmethod tell the bridge the type of the method argument, and declare a class method (Python does not have class methods).

@typedAccessor('i')
def setQuantity_(self, anInt):
    self.quantity = anInt
    
@classmethod
def cageWithPetNamed_(cls, aName):
    return cls.alloc().initWithPetNamed_(aName)

Similarly for Ruby, the next example derives a Ruby class from NSObject, and uses the kvc_accessor attribute accessor to indicate that the instance variable flagAutoUpdateenabled is key-value coding compliant.

class MyClass < NSObject
  kvc_accessor :flagAutoUpdateEnabled
  ...
end

Probably the most significant change to RubyCocoa and PyObjC in Leopard is that both use metadata generated by the gen_bridge_metadata tool to do full fidelity bridging of the APIs. In Leopard, each framework on the system includes an XML file (known as a BridgeSupport file) that describes all of the additional type information that cannot be gleaned directly at runtime. The BridgeSupport project home page is hosted at the Mac OS Forge website.

Creating Ruby and Python Applications with Xcode

Ruby and Python have full support for application development in Xcode 3. Starting a new project with one of these languages is as easy as with Objective-C or Java. Figure 1 shows the New Project Assistant, with project templates for Ruby or Python applications.

New Project Assistant

Figure 1: New Project Assistant

As an example, let's look at a RubyCocoa application called RSSPhotoViewer, which was created from the Cocoa-Ruby Application project template. RSSPhotoViewer is a cool little program that takes a URL and displays photos hosted at that URL in an IKImageBrowserView. You can find the source for the RSSPhotoViewer example on the Darwin Source Code website.

In the generated file, main.m, you see the Objective-C main function.

int main(int argc, const char *argv[])
{
  return RBApplicationMain("rb_main.rb", argc, argv);
}

This bit of code probably looks familiar. It simply calls the function RBApplicationMain, passing it the name of the Ruby file to run, and the application's launch arguments. Looking at the second generated file, rb_main.rb, we see the following code:

def rb_main_init
  path = OSX::NSBundle.mainBundle.resourcePath.fileSystemRepresentation
  rbfiles = Dir.entries(path).select {|x| /\.rb\z/ =~ x}
  rbfiles -= [ File.basename(__FILE__) ]
  rbfiles.each do |path|
    require( File.basename(path) )
  end
end

if $0 == __FILE__ then
  rb_main_init
  OSX.NSApplicationMain(0, nil)
end

The Ruby function rb_main_init first gets the path to its own bundle by calling across the bridge, into the OSX module. It builds an array of all Ruby files in its bundle, subtracts itself from the array, and executes the require function for each remaining Ruby file. It then calls across the bridge again, this time to OSX.NSApplicationMain. This is the standard startup code for a Cocoa-Ruby application.

There are only three source files in this example project—two of which are generated by the Xcode New Project Assistant. The third Ruby file, RSSWindowController.rb, is where all the action is. Before looking at that source file, it's important to notice that this Cocoa-Ruby application has a nib file that you edit using Interface Builder, just as you do with a Cocoa application. Figure 2 shows the RSSWindowController class connected to the IKImageBrowserView in the main window. This connection will be made when the objects are loaded from the nib file, just like a regular Cocoa application.

Interface Builder Connection

Figure 2: Interface Builder Connection

The third and last source file, RSSWindowController.rb, contains all of the Ruby code to implement this application. Writing a Cocoa-Ruby application feels a lot like writing a Cocoa application in Objective-C. The next code fragment shows how simple it is to derive a Ruby class from a Cocoa Objective-C class, NSWindowController.

  ...
class RSSWindowController < NSWindowController
  ib_outlet :imageBrowserView
  
  def awakeFromNib
    @cache = []
    @imageBrowserView.setAnimates_(true)
    @imageBrowserView.setDataSource_(self)
    @imageBrowserView.setDelegate_(self)
  end
  ...

The attribute accessor ib_outlet is a hint to the bridge that the instance variable imageBrowserView is an outlet created in Interface Builder. The code snippet also shows how the awakeFromNib method is overridden in Ruby. Derivation and overriding methods both simply use standard Ruby; the bridge takes care of the rest.

The pure Ruby class RSSPhoto is also defined in RSSWindowController.rb. This class, which represents the individual photos shown in the IKImageBrowserView shows how easy it is to implement an Objective-C protocol (IKImageBrowswerItem in this example) on the Ruby side of the bridge.

class RSSPhoto
  attr_reader :url
  
  def initialize(url)
    @urlString = url
    @url = NSURL.alloc.initWithString(url)
  end
  
  # IKImageBrowserItem protocol conformance
  
  def imageUID
    @urlString
  end
    
  def imageRepresentationType
    :IKImageBrowserNSImageRepresentationType
  end
  
  def imageRepresentation    
    @image ||= NSImage.alloc.initWithContentsOfURL(@url)
  end
end

The RSSPhotoViewer project, a full Mac OS X application consisting of just over 80 lines of custom Ruby code—code that freely mixes pure Ruby with Cocoa classes, including language features like inheritance and method overriding—shows the power of scripting language bridges.

Get Started with Leopard

The next generation of the world's most advanced operating system is now available. Tap into the innovative technologies of Mac OS X Leopard and design your products with new and compelling features. With an ADC Premier or Select Membership, you have a range of developer resources from Apple engineers and experts, including ADC on iTunes, Coding Headstarts, the ADC Compatibility Labs and more. Learn how ADC Memberships provide you Apple expertise. From code to market.

Updated: 2008-03-11

 
 
 

Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2009 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice