RubyCocoa 今日のコミット 2013-11-16
テーブルビューのスクロール時にクラッシュすることがあるの件の続き。
ということで、Objective-C -> Ruby オブジェクトのキャッシュを無効にする方法を導入した。
- OSX::ObjCIDに#hash, #eql?, #== を定義。(r2489)
- OSX::ObjCIDから#cloneを削除。(r2490)
- OSX::ObjCID#dupでのSEGVを修正。(r2491)
- キャッシュ制御用のグローバル変数 $RUBYCOCOA_USE_OC2RBCACHE を導入。(r2493)
- 10.4 Tiger用のビルドスクリプトや外部ライブラリを削除。((r2496, r2497)
- サンプルコードのビルドエラーや実行時エラーを修正。 r2498, r2499)
キャッシュを無効にしたとき、Objecitve-Cのid型の値が同じで、RubyのVALUEのIDが異なる、というオブジェクトが生成されることになる。今まではキャッシュ経由で同じIDのVALUEを割り当てるようにしていた。(CF系など、もともとキャッシュ対象外となっているクラスもある)
ID値が異なっていることにより問題あるケースというのが思いつかなかったのだけど、Hash使ったときには同じオブジェクトとみなしてほしいだろうと考えたので、#hashや#eql?を再定義して内部のObjective-Cのid値に従うようにした。その作業中に、#cloneができるのは基本的にまずかろう(必要ならObjective-Cのcopy系メソッドを使うことを推奨)と思ってundefしたのと、NSURL#dupすると落ちるので直した。これ、メモリの確保をrb_define_alloc_func()で関数を指示してなくって、initializeで行ってるのが原因ぽいんだけど、なんでこれずっと直されてなかったんだろ…?RubyCocoaの初期開発時には、Rubyにrb_define_alloc_func()なかったからそのまんまになってたてことなのかな。
OSX::ObjCIDは、OSX::NSObjectなどのObjective-CのクラスのRuby界での親になっているクラス。
で、本題のキャッシュ無効の制御なんだけど、グローバル変数を使う形にした。OSX::RubyCocoaモジュールを導入してGC.enableみたいにしてもいいんだけど手間のわりにメリットがないなあと。
使い方は、初期値がtrueになってるのでfalseにするだけ。たとえば、rb_main.rbで
require 'osx/cocoa' def rb_main_init : end $RUBYCOCOA_USE_OC2RBCACHE = false rb_main_init
としてやれば、そのアプリではキャッシュが無効になる。テーブルビューを使ったサンプルプログラムで、この手法で例のクラッシュが起きなくなったことを確認。
中の動作としては、キャッシュが無効にされたときに
- 既存のキャッシュをすべて捨てる。
- 新しいキャッシュへの登録を行わない。
ということになっている。再度trueにすればまた有効な状態で動作する。