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.
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.
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