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

クラスをいじってみる

またまた抜粋

  # MacRuby版
  it 'Objective-Cのクラスを書き換えられること' do
    lambda { eval 'class Sloth; def name; "nisefaultier"; end; end' }.should.not.raise
    obj = Sloth.alloc.init
    obj.name.should.be.eql "nisefaultier"
    # @obj.name.should.be.eql "nisefaultier"
  end
  # RubyCocoa版
  it 'Objective-Cのクラスを書き換えられること' do
    lambda { eval 'class Sloth; def name; "nisefaultier"; end; end' }.should.not.raise
    obj = Sloth.alloc.init
    obj.name.should.be.eql "nisefaultier"
    @obj.name.should.be.eql "nisefaultier"
  end

違いは一点、MacRuby版でクラスの定義を書き換えたとき、新しく作られるオブジェクトには影響するけど、既にあるオブジェクトには影響しない。が、RubyCocoa版はクラスの定義を書き換えるとそのクラスの全てのオブジェクトに影響する。面白い。Rubyは全てのオブジェクトがメタクラスのオブジェクトを見てるけど、ObjCのオブジェクトはそれぞれが関数ポインタか何かでメソッドを保持してるってことかな?RubyCocoaにおける「Cocoaのオブジェクト」って結局「(ObjCのオブジェクトを内部に持った)Rubyのオブジェクト」だけど、MacRubyのオブジェクトは全てが「ObjCのオブジェクト」なので、若干CRubyと挙動が違ってくるみたい。

ところで、上のコードはインスタンスメソッドを書き換えてるんだけど、ふと思い立ってこんなコードを書いてみた。

$ cat mrb_class.rb 
framework 'build/Release/Namake.framework'

p Sloth.new

class Sloth
  def self.new
    "string"
  end
end

p Sloth.new
$ macruby mrb_class.rb 
#<Sloth:0x80063c740>
"string"

MacRubyだと、Sloth::newの実装が差し換えられた。RubyCocoaだとどうだろう。

$ cat rbc_class.rb
require 'osx/cocoa'
include OSX

bundle = NSBundle.bundleWithPath('build/Release/Namake.framework')
bundle.load

p Sloth.new

class Sloth
  def self.new
    "string"
  end
end

p Sloth.new
$ ruby rbc_class.rb 
#<OSX::Sloth:0x2385ac class='Sloth' id=0x3734a0>
"string"

あれっ。別に普通だ。同じだった。というのも、昔RubyCocoa+RSpecでごにょごよやってたときに、Cocoaのクラスにstubを仕込んだら「それはObjC側の実装だから勝手に変えるな」みたいに怒られた記憶があるんだけど、んー、この感じだと特に問題無さそう?っていうかあれか、RSpecのstub!はもっと中で複雑なことしてるんだよなきっと。こんな単純な話じゃなくて。後で読んでみよう。もしかしたらそこが「RubyCocoaには無理だけどMacRubyならできる」とこかもしれないし、逆にMacRubyだと下手にいじると大変なことになる罠だったりするかもしれないし。

試してないこと

意図的に上の例では抜いてるけど、MacRubyの大きな特徴と言えば、「キーワード付き引き数」。そもそもそんな機能自体Rubyには無いので、これを上手く使えたら大分面白いことになりそう。あと「CocoaのクラスのサブクラスをRuby側で定義する」とか。MacRubyは何度も言うけど「全部がNSObjectのサブクラス」なので、ぶっちゃけ普通のRubyスクリプトを書くだけでもそういう風になってるんだけど、RubyCocoaはそうではないので、なんか違いがあるんじゃないかと思ってみたり。

そう言えば

MacRubyではオブジェクトを「release」することができない。undefined methodで例外が出る。まぁそもそもGC有りなのでオブジェクトにreleaseを送っても無意味なんだけど。だから存在する必要がないし「undefined」で当然か。ちなみに、RubyCocoaだとCocoaのオブジェクトにはちゃんとreleaseメッセージを投げられる。もっともRubyのGCでオブジェクトが開放されるときに自動的に呼ばれてるはずなので、RubyCocoaのコードでreleaseする必要もやっぱりない、はず。むしろ予想外のバグになりそうなのでやらない方がいいのかも[要出展]。