すでに述べたように、MacRubyの第1の動機は、RubyからCocoaへのアクセスの効率を上げることにある。だがその目的を達成するために、大胆にRubyランタイムに手を加えたため、純粋なRuby環境としてみた場合にも、興味深い変更がいろいろとある。その辺りを見てみよう。

すべてのRubyオブジェクトはNSObject

MacRubyでは、RubyからObjective-Cのオブジェクトにアクセスするときは、プロキシクラスを使わずに、直接アクセスできることは紹介した。そのときにRuby上からCocoaのクラスの継承階層を調べたが、これをもう一度見てみよう。たとえば、Cocoaで文字列を表すNSStringのクラス階層は次のようになっている。

irb(main):010:0> NSString.ancestors
=> [NSString, Object, NSObject, Kernel]

プロキシクラスがないことは分かるが、次のところにも注目してほしい。NSStringの親クラスは、RubyのObjectクラスである。そして、Objectクラスの親クラスは、Objective-CのNSObjectクラスになっている。なんと、RubyのルートクラスたるObjectクラスが、Objective-CのルートクラスであるNSObjectを継承しているのだ。

実はMacRubyでは、すべてのRubyオブジェクトはCocoaのNSObjectを継承するのだ。たとえば、Rubyのコアクラスである、Regexpクラスの階層を見てみよう。

irb(main):011:0> Regexp.ancestors
=> [Regexp, Object, NSObject, Kernel]

純粋なRubyのクラスなのに、親クラスの中にNSObjectが含まれている。このように、すべてのRubyオブジェクトはCocoaのオブジェクトになっているのだ。

RubyとObjective-Cの間でのToll-free bridege

それに加えて、RubyのString、Array、Hashといった基本的なクラスは、さらに特別な階層が割り当てられている。それぞれ、CocoaのNSString、NSArray、NSDictionaryというクラスから継承されているのだ。

irb(main):012:0> String.ancestors
=> [String, Comparable, NSString, Object, NSObject, Kernel]
irb(main):013:0> Array.ancestors
=> [Array, Enumerable, NSArray, Object, NSObject, Kernel]
irb(main):014:0> Hash.ancestors
=> [Hash, Enumerable, NSDictionary, Object, NSObject, Kernel]

つまりMacRubyでは、文字列はRuby環境であっても、実体はNSStringということになる。同様に、配列の実体はNSArrayだし、ハッシュの実体はNSDictionaryだ。

なぜ、このようなことを行ったのか? それは、Cocoaメソッドを呼び出すときに、引数に文字列や配列を持つもののパフォーマンスを上げるためだ。たとえば、次のようなコードを考えてみよう。

button = NSButton.alloc.initWithFrame(
        NSRect.new(NSPoint.new(10, 10), NSSize.new(80, 80)))
button.title = 'Hello World'

これは、ボタンを表すNSButtonをインスタンス化し、タイトルとして「Hello World」を設定するコードだ。注目してほしいのは、3行目のタイトルを設定するところだ。ソースコード中にある「'Hello World'」は、RubyのStringオブジェクトになる。それに対して、NSButtonのタイトルは、CocoaのNSStringオブジェクトでなくてはならない。

このとき、通常の言語間のブリッジであれば、Rubyの文字列からCocoaの文字列へと変換を行わなくてはならない。だが、MacRubyではすべてのRubyオブジェクトはCocoaのオブジェクトであり、Rubyの文字列はCocoaの文字列である。従って、変換のコストなしでこの呼び出しを行えるのだ。

このような仕組みを、Mac OS XではToll-free bridege(※2)と呼ぶ。変換の手間がかからない、無料で渡れるブリッジという意味だ。Mac OS Xで実現されているToll-free bridegeの実例としては、Cocoaと、C言語で書かれたCore Foundationと呼ばれるフレームワークとのブリッジがある。

つまり、MacRubyではRubyとObjective-Cの間でのToll-free bridegeを実現している、と言えるだろう。

Objective-C 2.0のガベージコレクタの利用

すべてのRubyオブジェクトをCocoaオブジェクトにした恩恵は、他にもある。Objective-C 2.0のガベージコレクタを使えるようになったのだ。

Objective-Cでは、C言語ベースということもあり、長らくガベージコレクタは実装されていなかったのだが、Objective-C 2.0でようやく導入された。特徴としては、保守的であり、マーク・アンド・スイープを用いており、世代別GCもサポートしている。

これらの特徴のうち、保守的であるというところは、C言語ベースの言語にはついて回るものだ。だが、MacRubyの場合、Rubyという閉じた言語を相手にするので、この制約はそれほど気にしなくともよいだろう。マーク・アンド・スイープによるコレクションは、現行のRubyと同じである。

最も気になるのは、世代別GCが導入可能になったという点だろうか。世代別GCでは、新世代から参照されたことを検出するための、ライトバリア(※3)が必要になる。Objective-Cのライトバリアは、Objective-CランタイムのAPIを使っており、コンパイルの時点で付加される。この辺りの効率の良さが、Rubyネイティブのものではなく、Objective-Cのガベージコレクタ(※4)を使うという判断になったようだ。

MacRubyのポテンシャルには期待

RubyからCocoaにアクセスするために、MacRubyがどのようなアプローチをとっているのか、詳しく紹介してみた。パフォーマンスを上げるという目的のもとに、RubyランタイムとObjective-Cランタイムを絶妙に融合させているのが分かるだろう。これにより、ガベージコレクションの効率が上がるかもしれないという、うれしい副作用も付いてきた。

MacRubyの問題点としては、RubyCocoaとソースコードの互換性がないことが挙げられるだろう。長い歴史と実績を持つRubyCocoaの資産を活かせないのはもったいない。何らかの解決策が欲しいところである。

いずれにせよ、非常に魅力的なRuby環境であることには違いない。MacRubyとRubyCocoaがお互い切磋琢磨して、よりよいMac OS Xのためのスクリプティング環境ができ上がることを期待しよう。

※2 Toll-free bridege
Toll-free bridegeに関しては、「【コラム】ダイナミックObjective-C」の第38回「Toll-free bridge(1) - 変換コスト0のブリッジ」を参照。

※3 ライトバリア
ライトバリアについては、Apple Developer Connectionの「Garbage Collection Programming Guide: Architecture」を参照。

※4 Objective-Cのガベージコレクタ
Objective-Cのガベージコレクタについては「【コラム】ダイナミックObjective-C」の第96回「ガベージコレクション (1) - GCのためのAPI」を参照してほしい。