MacRuby: The Path Forward
MacRuby is quietly making progress. Destined to replace RubyCocoa as the de facto standard for writing Cocoa applications in Ruby; it’s shaping up to become more than just a fun experiment, it’ll soon be a viable choice.
Burn Baby Burn
Unlike RubyCocoa, MacRuby is based on Ruby 1.9, powered by the YARV bytecode interpreter, resulting in significantly faster execution times. From the “Why MacRuby” page:
In MacRuby, all Ruby classes and objects are actually Objective-C classes and objects. There is no need to create costly proxies, convert objects and cache instances. A Ruby object can be toll-free casted at the C level as an Objective-C object, and the Ruby VM can also handle incoming Objective-C objects without conversion.
Also, the primitive Ruby classes, such as String, Array and Hash in MacRuby have been re-implemented on top of their Cocoa equivalents, respectively NSString, NSArray and NSDictionary. As an example, all strings in MacRuby are Cocoa strings, and can be passed to underlying C or Objective-C APIs that expect Cocoa strings without requiring a conversion. And it is possible to call any method of the String interface on any Cocoa string too.
That’s exciting news. MacRuby applications significantly outperform RubyCocoa applications and aim to enable the creation of full-fledged Mac OS X applications which do not sacrifice performance in order to enjoy the benefits of using Ruby.
Syntax
Considering String, Array, and Hash/Dictionary require no conversion, we get both the Ruby methods and the Objective-C methods without needing to cast back and forth.
#MacRuby
components = "foo/bar/baz".pathComponents
# RubyCocoa
components = "foo/bar/baz".to_ns.pathComponents
# or...
components = OSX::NSString.stringWithString("foo/bar/baz").pathComponents
#Objective-C
NSArray *components = [@"foo/bar/baz" pathComponents];
# or...
NSArray *components = [[NSString stringWithString:@"foo/bar/baz"] pathComponents];
And the biggest change is probably keyed arguments. I’ve grown to like keyed arguments in Objective-C because of their descriptive nature. Normally you can look at a method definition and determine what’s what.
def fetch_url(url, timeout)
...
end
However, unless you look up the method definition in the source or documentation, you don’t fully understand what the second argument encompasses during invocation.
obj.fetch_url("http://foo.com", 60)
This is where keyed arguments come into play. Take a look at how this method would be composed in Objective-C.
- (void)fetchURL:(NSURL *)url timeout:(float)theTimeout {
...
}
[obj fetchURL:[NSURL URLWithString:@"http://foo.com"] timeout:60.0];
And finally, we could write this method in MacRuby with the new keyed arguments syntax.
def fetch_url(url, timeout:timeout)
...
end
obj.fetch_url("http://foo.com", timeout:60.0)
We get the added benefit of clarity at the cost of verbosity. And finally, there is an additional nuance: CamelCase or under_score. I think I’ve settled with camelCase for now, because it’s not possible (practical ATM) to use underscored Objective-C methods, however, I’ve been told it’s being considered.
The Big Macs: macruby, macrake, macirb, macgem
Once you’ve set yourself up with MacRuby, you should know your programs are given a prefix of mac unless you specify a different prefix when compiling by hand. This means we’ll need to use macruby to invoke our Ruby scripts written for MacRuby.
In a completely convoluted example, lets grab the name of every application running on our machine; print it’s application name, and replace “Applications” in it’s path with “FOO”.
framework 'AppKit'
NSWorkspace.sharedWorkspace.launchedApplications.each do |app|
appname = app["NSApplicationName"]
fullpath = app["NSApplicationPath"]
puts appname
puts fullpath.stringByReplacingOccurrencesOfString("Applications", withString:"FOO")
end
Save this file and run with macruby thisfile.rb. You should see a list of applications running and their paths, with FOO replacing Applications. Got it? Schweet!
framework is a new method of Kernel that allows you to import any framework, including your custom frameworks. In this example, we’re including the AppKit framework and iterating over the launched applications.
Gems: A ruby in the rough
At the time of writing this article, it’s not possible to use rubygems or macruby gems in the traditional require and run method. However, you can vendor your gems and include them directly which should work perfectly fine. This is a bug that will be addressed very soon.
Get Involved
MacRuby is an official effort by Apple to make Ruby a first class citizen in Cocoa. It’s still very early in development and you’re definitely encouraged to start playing with it, writing about it, and getting involved in the project. And one final note, you can join the very helpful #ruby-osx channel on freenode.net if you’re looking for assistance.
Sorry, comments are closed for this article.


Discussion
Yay MacRuby!
Can’t wait for it to be on the iphone one day…one year… :)
You can use NSString methods on Ruby Strings, or call Ruby methods on NSStrings. The same goes for NSDictionary (Hash) and NSArray (Array), making MacRuby a truly powerful tool to write Cocoa apps in Ruby.
@kourge: great additions, thanks!
In regards to my example, I just wanted to show keyed arguments.
Also, I believe is* method can be treated as ? methods in RubyCocoa as well.
First of all @Justin, very nice article!
Yes in RubyCocoa the isFoo methods are also translated to foo? as are the setters: setFoo() becomes foo=().
@kourge: The examples you posted are not completely true. You can omit the last underscore, so you’re first method would become:And the second example has been deprecated for a long time in favour of #objc_send:
And finally ducktyping for the basic classes have been supported in RubyCocoa as well for a while now. So in RubyCocoa the gsub example works as well:
The more important thing in MacRuby to note about the implementation in MacRuby is that the results don’t have to be bridged and thus is toll free.
Eloy
I’m really excited about MacRuby and try it out before reverting to RubyCocoa if I find that it isn’t mature enough yet to work with everything I want.
I have started a Google group for “Ruby and Cocoa” (ruby-and-cocoa) to provide a single place for RubyCocoa and MacRuby developers to discuss things online.
I also started a weblog where I post some basic tutorials and screencasts on MacRuby and RubyCocoa (about half and half) for other complete beginners such as myself. This can be found at foolsworkshop.com/rubycocoa/
Great article. I’m really excited about MacRuby.
One quick point: In your comparison of MacRuby/RubyCocoa/Objective-C, the Objective-C version can be written as:
Which is a lot nicer.
What about threading?
@bob wow, I didn’t know that. That certainly looks a lot better. I’ll update the post.
@Jon In Ruby 1.9, threads are native POSIX. Multiple threads can callback to the runtime according to the MacRuby documentation.
I’m still learning and found your article searching for MacRuby. Great job. Thanx.