MacRubyとRubyCocoaの微妙な違い (1)の続き。

フレームワークの読み込み

まず最初に違うのは、framework。これはRubyCocoaで書くと多分こんな感じ。

require 'osx/cocoa'

def framework(name)
    OSX.require_framework name
rescue
    bundle = OSX::NSBundle.bundleWithPath(name)
    bundle.load
end

MacRubyのframeworkは名前じゃなくてパスを渡してやるとそのFrameworkを読み込んでくれるけど、OSX::require_frameworkは名前しか受け付けない、つまりパス上にあるFrameworkしか探さない。ので、任意のパスにあるFrameworkを動的に読み込みたいときは、NSBundleのクラスメソッドを使ってバンドルのオブジェクトを作っておいて、それのloadメソッドを呼ぶ必要がある。ちなみにこれはRubyCocoaではなくCocoaの方の機能なのでもちろんMacRubyでも使える。

Stringの扱い

RubyCocoa版の方で、Slothクラスのname、setNameを呼び出してるとこだけ抜粋。

  it 'Objective-Cのメソッドを呼べること' do
    @obj.name.should.be.nil
    # lambda { @obj.name = "faultier" }.should.not.raise
    lambda { @obj.name = "faultier".to_ns }.should.not.raise
    @obj.name.should.be.== "faultier"
    # @obj.name.should.be.eql "faultier"
    # @obj.name.should.be.kind_of String
    @obj.name.should.be.kind_of NSString
    lambda { @obj.setName "ふぁうるてぃあ".to_ns }.should.not.raise                     
    @obj.name.to_s.should.be.== "ふぁうるてぃあ"
  end

コメントアウトしてるところは、実行するとテストが通らないところ。RubyCocoaではStringとNSStringは全く別のクラスなので、引き数にNSStringが来るはずのCocoaのメソッドを呼ぶときはRubyのString#to_nsを呼んで一度NSStringのオブジェクトに変換してから渡してやる必要がある。また、当然kind_of?(String)はfalseになる。面白いのは、

"string".to_ns == "string" #=> true

になるってこと。==のときは変換かけてから比較するらしい。もう一つ面白いのは、asciiのときにはStringとNSCFStringのeql?がtrueになるのに、日本語のときは何故かfalseになる。$KCODEを指定してやっても駄目。ふぅん。あれ。ていうか、"string".to_ns.eql?("string") ってそもそもtrueじゃないよな。なんでshould.be.eql("string")だと通るんだろう。通っちゃう方がおかしい気がする。

全然そんなことなかった。@obj.name.to_s.should.be.eql("faultier")にしてた。そりゃ通るわ!というわけで訂正。==はNSStringとStringの変換を行なった後比較するけど、eql?およびequal?はもう少し厳密で、NSStringとStringを別のものとして扱う。マルチバイトでも特に違いはない。

ちなみに、MacRubyでは以下が全て成立する。

  it 'Objective-Cのメソッドを呼べること' do
    @obj.name.should.be.nil
    lambda { @obj.name = "faultier" }.should.not.raise
    @obj.name.should.be.== "faultier"
    @obj.name.should.be.eql "faultier"
    @obj.name.should.be.kind_of String
    @obj.name.should.be.kind_of NSString
    lambda { @obj.setName "ふぁうるてぃあ" }.should.not.raise
    @obj.name.should.be.== "ふぁうるてぃあ"
    @obj.name.should.be.eql "ふぁうるてぃあ"
  end

理由は簡単。

$ ruby -r'osx/cocoa' -e 'p String.ancestors'
[String, Enumerable, Comparable, Object, Kernel]
$ ruby -r'osx/cocoa' -e 'p OSX::NSString.ancestors'
[OSX::NSString, OSX::NSObject, OSX::OCObjWrapper, OSX::NSKeyValueCodingAttachment, OSX::NSKVCAccessorUtil, OSX::ObjcID, Object, Kernel]
$ macruby -e 'p String.ancestors'
[NSMutableString, NSString, Comparable, NSObject, Kernel]
$ macruby -e 'p NSString.ancestors'
[NSString, Comparable, NSObject, Kernel]

見ての通り、MacRubyではRubyのStringはNSStringのサブクラスになってる。てことは、Slothクラスのname/setNameにはRubyのStringオブジェクとがそのまま渡ってそのまま返ってきてるはず。この辺は流石にMacRubyの方がずっと自然に感じられる。

さらに続く。なんか妙に筆が進んでしまった。