mruby-cfuncを読んだので次はmruby-cocoaを読みます。ちなみに、mruby-cocoaに含まれているコードだけではmruby-cocoaの機能を説明しきれないところについて、一部mobiruby-iosのコードも掲載しています。
mruby-cfuncと同様にtest/main.mからスタートします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | - | | ! - | | | | | | | | | | | - | ! | | - | ! ! |
|
mruby-cocoaはmruby-cfuncを使っているため、udにmruby-cfuncの情報を入れる必要があるようです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| - - | ! | - | - | ! ! | | | | | | | | | | | ! |
|
まず初めに渡されたmrb_stateをグローバル変数に設定しています。mruby-cocoaでは複数のmrubyインスタンス(mrb_state)をサポートしているようです。スレッドごとにmrubyインスタンスを持てるようにしているのでしょうか。
その後、Objective-C、Cocoaとの接続の初期化が行われているようです。
見た感じ、Objective-C的にオブジェクトが削除されるときにmruby的にもオブジェクトが削除されるための処理を行っているようなのですが、Objective-Cに詳しくないのでパスします。
Cocoa::Objectの定義とメソッドの登録を行っています。各メソッドがどのように動くかは後で見ることにします。
Cocoa::Blockの定義を行っています。
Cocoa::StructとCocoa::Constを定義し、const_missingをオーバーライドしています。Constの方でmethod_missingも登録しているのは小文字で始まる変数への対応と思われます。
Cocoa::ObjectなどについてRubyで書いた方が書きやすい、という処理が追加で定義されています。どのように動くかについては以下で見ていきます。
ちなみに、init_cocoa()で何をやっているかはsrc/mrb/cocoa.rbを見ればよいというからくりはmruby-cfuncを読むの方をご参照ください。
mruby-cocoaの初期化が終わったので例によってテストコードを通してmruby-cocoaの使い方を見ていきます。
初めの疑問は
Cocoa::NSString
と書いたときにどう動いているかです。これまでにCocoa::NSStringの定義はどこにもありませんでした。ていうか、すべてのCocoaクラスに対してRubyのラッパークラスを定義するなんて非現実的すぎるでしょう:-P
答えは以下の部分です。
src/mrb/cocoa.rb
module Cocoa def self.const_missing(name) if ::Cocoa::Object.exists_cocoa_class?(name) return ::Cocoa::Object.load_cocoa_class(name) end raise "uninitialized constant #{name}" # ToDo: NameError end end
src/mrb/cocoa_object.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| - | | | | - | ! | | | ! |
|
どう動くかというと、
という動きになります。
次はメソッドの呼び出しです。test/mrb/cocoa_test.rbの先頭では以下のようなメソッド呼び出しが見られます。
Cocoa::NSString._stringWithUTF8String("string")
見落としそうですが、'_'が付いてます。この_stringWithUTF8Stringが呼び出されると以下のmethod_missingが反応します。
class Cocoa::Object def method_missing(name, *args, &block) if '_' == name.to_s[0, 1] return self.class.call_cocoa_method(:self, self, name.to_s[1..-1], *args, &block) else raise "Unknow method #{name}" end end
というわけでCocoaメソッドを呼ぶときは'_'を本来のメソッド名の頭に付けて呼ぶという規約のようです。
その後、Rubyで書かれたcall_cocoa_methodで呼び出すメソッド名を作成し、Cで書かれたcocoa_object_objc_msgSend()でObjective-CランタイムとFFIを用いてメソッドの呼び出しを行っています。
メソッド名を作成するということについて少し細くしておきます。cocoa_test.rbだとありませんがmobiruby-iosにあるhello.rbにあるような
alert = Cocoa::MyAlertView._alloc._initWithTitle "Hello", :message, "I am MobiRuby", :delegate, nil, :cancelButtonTitle, "I know!", :otherButtonTitles, "What's?", nil
という呼び出しに対して"initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:"というメソッド名を作成するということです。
mruby-cocoaでプロパティを参照する場合は以下のように書きます。
result = @test1[:prop1]
個人的には、
@test1.prop1
と書きたいところですが、そうするとRubyのメソッドなのかObjective-Cのプロパティなのかわからないので可読性を考えるとこちらの方がいいですね。
それはともかく、プロパティ参照が行われると、
と処理が流れます。
mruby-cocoaでObjective-Cのメソッドを定義するにはdefではなく、defineを利用します。複数引数のサンプルということでmobiruby-iosのhello.rbで定義されているメソッドを取り上げます。
define C::Void, :alertView, C::Pointer, :clickedButtonAtIndex, C::SInt32 do |me, index| if index.to_i == 1 app = Cocoa::UIApplication._sharedApplication url = Cocoa::NSURL._URLWithString("http://mobiruby.org") app._openURL url end end
というわけでdefineの引数は
と記述すればいいようです。また、メソッドの処理はブロックで記述し、引数はブロック変数で受け取るようです。
Cocoa::Object#defineの中身に入っていくと、
ということを行っています。
で終わろうと思ったのですが気になる点があるので続けます。気になる点は以下、
class Cocoa::Object def self.define(rettype_rb, *args, &block) ... closure = CFunc::Closure.new(rettype_rb, [CFunc::Pointer, CFunc::Pointer] + types) do |*a| self.new(a[0]).send(internal_method_name, *a[2, a.length-2]) end
a[0]って何が渡されるの?というのが気になる点ですが、self.newの実装である*1cocoa_object_class_new()を見るとidなようです。a[1]はメソッド名?
ブロックを定義するには以下のように書けばいいようです。
block = Cocoa::Block.new(CFunc::Int, [CFunc::Int]) { |i| i.value + 1 }
Cocoa::Block.newの引数は、
と指定するようです。
BridgeSupport(Cocoaで定義されている定数等の参照)は以下のように記述します。
Cocoa::Const::kCFAbsoluteTimeIntervalSince1904
参照のためには下準備が必要です。test/main.mに書いてあるBridgeSupportの初期化部分を掲載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | - | | | ! - | | | ! - | | ! - | | | | | | | | | |
|
といった感じに利用したい構造体、定数の情報を記述し、load_cocoa_bridgesupport()を呼び出す必要があります。実際の参照時はconst_missingが発生し*2参照処理が行われるようです。
今回はmruby-cocoaを読んできました。鍵となるのはconst_missingとmethod_missingを用いた動的なクラスロード、メソッド呼び出しのようです。
また、オブジェクト管理についてRubyでの管理とObjective-Cでの管理についてかなりめんどうなことをやっているように見られます。が、Objective-Cは使ったことがないので突っ込んだ解説ができません。理解が進む、つっこみなどをいただくなどしたら随時情報を追記していくようにしたいと思います。