きりかノート 3冊め

おあそびプログラミング

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 世界に出現するときには、RubyGC 用に retain するかどうかを判定し、必要なら retain、そうでなければ Ruby 側がオーナーシップを持つものとして、init での retainCount 1 を使うようにする。

で、今回はなにが問題かというと、プレースホルダを利用したクラスでは alloc 時と init 時に返してくるオブジェクト(Cocoa なら id 値)が異なることだ。すると RubyCocoa は、両方のオブジェクトについて「おまえらは Ruby GC の管理下だからな」ということで、マークする。このマークをつけられたオブジェクトは、Ruby から参照されなくなると、GC 時に release される。

今回のケースでは、

  1. alloc 時に プレースホルダにマーク
  2. init 時に 生成されたオブジェクトもマーク
  3. GC 時に プレースホルダは参照されていない(ふつう alloc.init と続けて書くので)から release される
  4. また同じクラスを alloc.init しようとすると、プレースホルダは dealloc されているのでクラッシュ

となってしまうわけだ。コードの修正はまたあとで。