RubyCocoa 昨日のコミット(2008.02.10)
ObjC オブジェクトに対応する Rubyオブジェクトが同一にならないことがある問題
r2174で追加された(最初から失敗していた)テストケースを調べた。これは、Nib のアウトレットの取り出し方法によって、同一のアウトレットの Ruby オブジェクトの id 値が異なるというもの。
RubyCocoa では、 ObjC オブジェクトとその Ruby 側のオブジェクトの関連を、ハッシュテーブルとして管理していて、それによって ObjC と Ruby のオブジェクトが一対一になるように制御している。それがうまくいっていないようだ。
原因は ObjC から Ruby に引き数として渡されたオブジェクトはキャッシュしないようになっていたため。そのため、
- Nib がロードされて、アウトレット(Ruby のインスタンス変数)に保存される。ここではキャッシュされない
- NSNib#instantiateNibWithOwner_topLevelObjects でアウトレットを取り出すとき、キャッシュにないため 新しい VALUE が生成される。その後この値がキャッシュされる。
となっていた。なので、今回は Nib 関連で見つかったのだけど、なにも Nib に限定して発生する問題ではない。
キャッシュするようにすれば直るのはわかったのだけど、わざわざ ocdata_to_rbobj() において Qfalse というある種のマジックナンバーで特殊処理をしているので、そのまま直してよいかわからず、ML に簡単な報告を投げた。そしたら、どうも問題なかったようで、lrz がコミットしてくれた(r2189)。さんきゅー。
[rubycocoa-devel:1313] で Laurent が言うように、このキャッシュシステムはけっこーナーバスで、ややこしい問題を発生させることがある。もともとラッパーオブジェクトの生成を抑制する(初期の RubyCocoa では毎回 VALUE を生成していたケースがあった)ことでパフォーマンスを向上させるのが目的だったのだけど、そこから派生してObjC<=>Rubyのオブジェクト対応を保証する仕組にまでなっている。
個人的には、同一判定を __ocid__ で比較する(文字列などは適切に #==(other)をオーバライドして定義することが必要)だけで良くって、ラッパーはどうなっててもいいと思っているのだけど、当時は賛同得られなかったしなあ。
キャッシュしてない他の場所も対応
上記の件と関連して思い出したので、以下も対処した。
- キャッシュされない rbobj_get_ocid() 呼び出しを rbobj_to_nsobj() に置き換え(R2190)
まだ何箇所か直したほうがよいところがありそうだろうけど、確認できたところから。rbobj_get_ocid() はかなり深いところにある機能で、ふつーはホイホイ呼ばれるような関数じゃない。(昔はそうじゃなかったので、以前からあった実装にはいくつか残っている)
この件は、[ruby-list:44464] の確認をしているときに、やばそうな箇所をいくつか見つけたので直さなくちゃと思っていたのだ。