きりかノート 3冊め

おあそびプログラミング

RubyCocoa 今日のコミット 2014-06-07 擬似オープンクラスの2.x対応

  • OSX.objc_classnames()を追加。 (r2530)
  • OSX.ns_import_all()を追加。require_framework時に呼ぶように。 (r2531)
  • テストコードの一部にns_importを追加。 (r2532)

ということで、やっとここまできたぜ。。

   % ruby2.1 install.rb test
      :
   Finished in 2.606181 seconds.

   417 tests, 2750 assertions, 12 failures, 5 errors, 0 pendings, 0 omissions, 0 notifications
   95.9233% passed

まだ未対応の文字列まわりを除けば、

   325 tests, 1994 assertions, 1 failures, 1 errors, 0 pendings, 0 omissions, 0 notifications
   99.3846% passed

と1F1Eまできた。ruby-2.0でも同様。

作業内容

RubyCocoaではObjctive-Cの同名のクラスがあると、class構文でRubyのクラスを定義したときにそのクラスをObjctive-Cのクラスと自動的にさし替えてしまう機能(さいきん擬似オープンクラスと呼ぶことにした)がある。詳しくは2月の記事見てください。

で、これがけっこうむちゃな手法で実現されててruby-2.0以降では使えない。で、対処として、

  • OSX.require_frameworkして、.frameworkをロードした後に
  • Objctive-CのクラスをぜんぶモジュールOSX以下のクラスとして定義する

という方法をとることにした。ずいぶんおおざっぱなやり方だけど、対象とするクラスの絞り込み条件などうまく思いつかなかったので、現状は全クラスが対象。そのうち見直すかも。

そのために新しいモジュール関数として

  • Objective-C全クラスを取ってくる機能としてOSX.objc_classnames()を
  • それらをクラス化するためにOSX.ns_import_all()を

導入した。全クラスといっても、Cocoa系でない"Object"などのObjective-Cクラスは対象から除いてるし、クラス化も名前が大文字ではじまるもののみにして、"_"ではじまるクラスなんかは除くようにしている。

OSX.objc_classnames()は1.8上であからさまに遅かったりするので、ns_import_all()もRubyじゃなくてObjective-Cで書き直すかもしれない。目的からしたらループまわすのは1回でよいわけだし。

require_frameworkでフレームワークをロードした場合はクラスを自動的にもってくるようにしたけれど、requireで.bundleを読んだときはそういうことはしていないのでOSX.ns_importが必要。テストコード上ではns_importを追加して対処。

残ってるテスト失敗

E - tests/tc_nsarray.rb:test_grep

Array#grepがNSNumber#to_strを呼び出そうとしてメソッドがないのでエラーになる。Arrayの中身をNSStringとかにしてもおきる。NSNumber#responds_to? :to_str はfalseなのでRegexp#===の中では呼ばれないと思うんだけどなあ。check_funcall_respond_to()がTRUEってことなんかね。よくわらん。

F - tests/tc_bs.rb:test_framework_loaded

明示的に読み込みしてないはずのAdreeBook.frameworkが読み込まれてる。たぶん上記のns_import_allの副作用。

これはもうちょっと調べたら解決しそうな気がする。

(6/14追記: 解決した)テストで使ってるObjective-C用のコード入れてるobjc_test.bundleがAddressBook.frameworkにリンクしていた。おそらくフレームワーク内のクラスがns_import_all -> OSX.ns_import AB... -> NSClassFromString()されてフレームワークがロードされるのだろう。テストで使ってないWebKitがロードされてないよね?というテストに書き換えて解決。