RubyCocoa - retain 漏れの探索 (その2)プレースホルダなんてあったね
RubyTypingTutor がクラッシュする件。[rubycocoa-devel:428]
ocm_retain_result_if_necessary() での alloc 時の振る舞いを変更すればクラッシュしなくなるってことは、Ruby 側からの alloc 呼び出しに関連した部分なんだろう。BigLetterView.rb での alloc は
129 @attributes = NSMutableDictionary.alloc.init
214 an_image = NSImage.alloc.init
の2箇所だ。これを alloc.retain.init に変更して試してみよう。その結果、 NSMutableDictionary.alloc.init にどうも問題があるらしいことがわかった。NSString など、このへんの Foundation の基本的なクラスの alloc 処理ってなんか特殊処理があったよな。あー、Cocoa のプレースホルダオブジェクトだ。
プレースホルダについては、mkino さんによる Objective-C optimization の翻訳「メモリ確保の基本と Foundation」 がわかりやすい。
RubyCocoa では、よりうまく利用できるように、0.4.2 から 0.5.0 にかけてメモリ管理モデルを変更した。新しいモデルは、ある Cocoa オブジェクトが Ruby 世界に出現するときには、Ruby の GC 用に retain するかどうかを判定し、必要なら retain、そうでなければ Ruby 側がオーナーシップを持つものとして、init での retainCount 1 を使うようにする。
で、今回はなにが問題かというと、プレースホルダを利用したクラスでは alloc 時と init 時に返してくるオブジェクト(Cocoa なら id 値)が異なることだ。すると RubyCocoa は、両方のオブジェクトについて「おまえらは Ruby GC の管理下だからな」ということで、マークする。このマークをつけられたオブジェクトは、Ruby から参照されなくなると、GC 時に release される。
今回のケースでは、
- alloc 時に プレースホルダにマーク
- init 時に 生成されたオブジェクトもマーク
- GC 時に プレースホルダは参照されていない(ふつう alloc.init と続けて書くので)から release される
- また同じクラスを alloc.init しようとすると、プレースホルダは dealloc されているのでクラッシュ
となってしまうわけだ。コードの修正はまたあとで。