きりかノート 3冊め

おあそびプログラミング

テーブルビューのスクロールで落ちることがある件の調査 (2)

プロジェクトテンプレートが片づいたので、例の「NSTableViewをスクロールしているとEXC_BAD_ACCESSで落ちる」件の調査。前回調べたとき(2012/04)までで、

  • 落ちる場所は objc/OverrideMixin.mのovmix_ffi_closure()のrb_obj_is_kind_of()
  • ここでObjC側からのメソッド呼び出しのパラメータをVALUEに変換したものが壊れてるぽい

ということがわかった。

ovmix_ffi_closure()はObjC側からRubyオブジェクトのメソッド呼び出しなので、どのメソッドのどのパラメータで起きているかをまず確認しよう。その結果、

  • tableView:objectValueForTableColumn:row: (NSTableViewDataSource Protocol)
  • tableView:willDisplayCell:forTableColumn:row: (NSTableViewDelegate Protocol)

のtableColumnでエラーになっていることがわかった。

調べやすくするため、単純にテーブルビューだけもつプログラムを作成。AppDelegateをこんな感じに。

   attr_accessor :data

   def awakeFromNib
       self.data = Array.new
       50_000.times do
           self.data << rand.to_s
       end
   end

   # NSTableViewDataSource

   def numberOfRowsInTableView(tableView)
       return self.data.size
   end

   def tableView_objectValueForTableColumn_row(tableView, column, row)
       return self.data[row]
   end

   # NSTableViewDelegate

   def tableView_willDisplayCell_forTableColumn_row(tableView, cell, column, row)
       return nil
   end

   # actions

   ib_action :startGC
   def startGC(sender)
       GC.stress = !GC.stress
   end

このプログラムで

  • スクロールすると落ちることがある(1度だけ再現、すげー時間かかる)
  • GC.stress下だとすぐ再現する。1つめのパラメータtableViewの処理中。
  • キャッシュを無効にすると、GC.stress下でも(なかなか?)落ちない
  • tableView, tableColumnをインスタンス変数に保持するとGC.stress下でも落ちない

ということになった。このことから

  • VALUEがfree()されているにもかかわらず、キャッシュoc2rbCache(objc/ocdata_conv.m)に残ってしまう

というバグの可能性が高い、という感触。最初のプログラムでtableColumnの処理で落ちてた(tableViewでは落ちない)のは、tableViewはアウトレットになっているからだろう。

通常ObjC側からきたオブジェクトはGC時にキャッシュから消されるようになっている(objc/cls_objcid.m:_objcid_data_free())なので、どういうケースならこうなるかを考えているところ。

続く。