【レポート】

「MacRuby」 - ひさしを借りて母屋を乗っ取るか? Mac OS Xの新たなRuby環境

3 Ruby環境としての特徴

    木下誠  [2008/03/17]

    すでに述べたように、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」を参照してほしい。

    関連記事

    関連サイト

    関連したタグ

    新着記事

    特設サイトの情報

      人気記事

      一覧

      イチオシ記事

      新着記事

      特別企画

      マイナビニュースマガジン