実際にMacRubyを動かしながら、どのようにCocoaの機能を呼び出すのか紹介していこう。適時、RubyCocoaとの差異の解説も交えていく。

まずはインタラクティブにRubyを操作するために、irbを起動しよう。MacRubyのirbは、標準ならば、/usr/local/bin/irbにインストールされる。

$ /usr/local/bin/irb
irb(main):001:0>

frameworkメソッド

始めに、Cocoaを利用可能にする。これには、MacRubyで新たに導入されたframrworkメソッドを使う。次のように入力することになる。

irb(main):002:0> framework 'cocoa'
=> true

これで、Cocoaのためのクラスがランタイムに追加された。frameworkは、MacRubyがKernelモジュールに追加したメソッドになる。RubyCocoaでは、Cocoa機能の読み込みには、requireメソッドを使っていた。

frameworkでは、Cocoaだけではなく、他のObjective-Cで書かれたフレームワーク、またC言語で書かれたフレームワークも読み込める。つまり、Mac OS Xのほとんどの機能を使えるわけだ。実際のところ、これは特にMacRubyの機能という訳ではなく、Leopardから導入されたブリッジサポート(※1)に機能によるものになる。

プロキシオブジェクトの排除

RubyからCocoaのオブジェクトを利用する準備は整った。早速試してみよう。

例として、Cocoaを使って音を鳴らしてみる。NSSoundというクラスを利用する。

irb(main):003:0> sound = NSSound.soundNamed('Blow')
=> #<NSSound:0x1217690>
irb(main):004:0> sound.play
=> 1

これが、RubyからのCocoaの呼び出しになる。

RubyCocoaと比べたとき、MacRubyの最も大きな特徴は、プロキシオブジェクトを作成しなくなったことだ。上記の例のように、RubyCocoaでCocoaのクラスにアクセスする際は、RubyからObjective-Cのクラスへのプロキシクラスを作成していた。これは、呼び出しに余分な手間がかかり、引数を変換するコストもかかり、パフォーマンスを上げるには大きな制約となる。

MacRubyでは、このプロキシオブジェクトを削除することにした。Rubyから直接Objective-Cのクラスにアクセスできるようにしたのだ。

この違いを確かめるために、Ruby環境上でのCocoaクラスを調べてみよう。RubyCocoaでは、先ほど音を鳴らすために使ったNSSoundは、次のような継承階層を持つクラスになっている。

>> OSX::NSSound.ancestors
=> [OSX::NSSound, OSX::NSObject, OSX::OCObjWrapper, OSX::NSKeyValueCodingAttachment, OSX::NSKVCAccessorUtil, OSX::ObjcID, Object, Kernel]

それに対して、MacRubyでのNSSoundの階層はこうなる。

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

MacRubyでは、プロキシとなるクラスがなくなっているところに注目してほしい。直接Objective-Cのメソッドにアクセスできるようにしたため、必要なくなったのだ。

このアクセスは、ソースコードを調べてみると、RubyランタイムからObjective-Cのランタイム関数を直接呼び出すことで実現しているようだ。RubyもObjective-Cも、ランタイムはC言語で書かれているので、このような混在をやりやすい。これにより、パフォーマンスの大きな改善が期待できるだろう。

キーワード付き引数

Objective-Cの文法の特徴は、メソッドの呼び出し方にある。例えば、Cocoaでウインドウを表すNSWindowの初期化を行うコードは、Objective-Cで書くと次のようになる。

NSWindow*   window;
window = [[NSWindow alloc] 
        initWithContentRect:frame 
        styleMask:NSBorderlessWindowMask 
        backing:NSBackingStoreBuffered 
        defer:false];

このメソッドは、initWithContentRect:styleMask:backing:defer:という名前になる。4つの引数をとるメソッドだ。Objective-Cのメソッドを呼び出すときは、コロンに注目してほしい。コロンの後ろに来るのが引数となる変数、そしてコロンの前にはラベルと呼ばれるものが来る。ラベルは引数の説明となるものだ。

MacRubyはこのObjective-Cの文法を、できる限りRubyで再現している。引数にキーを付けられるようにしたのだ。たとえば、先ほどのNSWindow初期化のメソッドは、MacRubyでは次のようになる。

irb(main):006:0> window = NSWindow.alloc.initWithContentRect(
        frame, 
        styleMask:NSBorderlessWindowMask, 
        backing:NSBackingStoreBuffered, 
        defer:false)

括弧の中に4つの引数が並んでいるが、2つ目以降の引数には、コロンで区切るキーを付けている。最初の1つはメソッド名として使われているので、ちょっと整合性がとれていないのが残念ではあるが。

ここでメソッド呼び出しの括弧を省略してみよう。さらに、第一引数をメソッド名と同じ行に書いてみる。すると、このようになる。

irb(main):007:0> window = NSWindow.alloc.initWithContentRect frame, 
        styleMask:NSBorderlessWindowMask,
        backing:NSBackingStoreBuffered,
        defer:false

かなり、Objective-Cの文法に近づいてないだろうか? これで、Objective-CからRubyに移行する場合でも、違和感なくメソッドを呼び出せるだろう。

ちなみに、キーの指定はコロンだけではなく、=>を使うこともできる。

irb(main):009:0> window = NSWindow.alloc.initWithContentRect(
        frame, 
        styleMask => NSBorderlessWindowMask, 
        backing => NSBackingStoreBuffered, 
        defer => false)

この構文は、Rubyにキーワード付き引数を導入したものと考えることもできるかもしれない。だが、呼び出し先としてObjective-Cのクラスを想定しているので、一般的なキーワード引数とは違う性質を持つ。まず、引数の順序を入れ替えることはできない。だが、キーの重複は許されることになる。これは、Objective-Cのメソッド呼び出しがキーワード呼び出しではなく、単なるラベルであることに起因している。

※1 ブリッジサポート
ブリッジサポートについては、「【特集】Mac OS Xの開発環境」内 の「(10) RubyCocoa、PyObjCとブリッジサポート」を参照してほしい。