1RubyCocoaプログラミングの基本 2============================= 3 4このドキュメントは、RubyCocoaプログラミングする上で知っておくべき基本事項を紹介します。 5RubyおよびCocoaの初歩をある程度学んでいる方が対象です。 6対話型Rubyインタプリタを使って、実際に確認しながら読むと良いでしょう。 7対話型Rubyインタプリタとしては以下のものが使えます。 8 9* irbコマンド 10* CocoaRepl (RubyCocoa付属サンプルアプリケーション) 11* RubyConsole (RubyCocoa付属サンプルアプリケーション) 12 13後半のGUI関連の確認には、irbよりもCocoaReplもしくはRubyConsoleが適しています。 14 15 16RubyCocoaのロード 17----------------- 18 19RubyCocoa のライブラリは以下のようにロードします。 20 21 require 'osx/cocoa' 22 23 24システム音を鳴らしてみる 25------------------------ 26 27まずは、動いた実感を味わうために、システム音を鳴らしてみましょう。 28 29 names = Dir['/System/Library/Sounds/*.aiff']. 30 grep(/([^\/]+)\.aiff/){ |i| $1 } 31 OSX::NSSound.soundNamed(names[0]).play 32 OSX::NSSound.soundNamed(names[1]).play 33 34以降は、RubyCocoa の動作の理解の助けになると思われる例を挙げていきます。 35説明の中で #=> の右側は実行結果として表示される文字列です。 36 37 38Cocoaクラス 39------------ 40 41 p OSX::NSObject #=> OSX::NSObject 42 obj = OSX::NSObject.description 43 p obj # => #<OSX::NSCFString:0x220c2e class='NSCFString' id=0x1103c10> 44 obj = OSX::NSObject.alloc.init 45 p obj #=> #<OSX::NSObject:0x1cad6 class='NSObject' id=0x5930c0> 46 47RubyCocoa では、Cocoa のクラスは OSX モジュール以下に定義されています。 48Cocoa クラスは、Ruby のクラスであると同時に Cocoa のオブジェクトとしても振る舞います。 49 50 51Cocoa オブジェクトの生成 52------------------------ 53 54Cocoa オブジェクトの生成には、Cocoa の各クラスのメソッドをそのまま使います。 55 56 obj = OSX::NSObject.alloc.init 57 str = OSX::NSString.stringWithString 'hello' 58 str = OSX::NSString.alloc.initWithString 'world' 59 60生成された Cocoa オブジェクトは、RubyCocoa 内部で OSX::ObjcID というクラスのオブジェクトに包まれています。 61通常、OSX::ObjcID クラスの存在を意識する必要はありません。 62 63 64オーナーシップとメモリ管理 65-------------------------- 66 67OSX::ObjcID のインスタンスは、かならず自分が包んでいる Cocoa オブジェクトのオーナーシップを持ちます。 68オーナーシップは、OSX::ObjcID のインスタンスが GC に掃除されるときに自動的になくなります。 69したがって、RubyCocoa ではオーナーシップの管理を気にする必要はありません。 70また、OSX::ObjcID というクラスの存在を意識する必要もありません。 71 72 str = OSX::NSString.stringWithString 'hello' 73 str = OSX::NSString.alloc.initWithString 'world' 74 75上の2つの例は、Objective-C ではオーナーシップを発生させるかさせないかという違いがありますが、オーナーシップを気にする必要のない RubyCocoa では大して違いはありません。 76retain、release、autorelease などのメソッドは、基本的に呼ぶ必要がありませんし、NSAutoreleasePool を作る必要もありません。 77 78* Cocoa オブジェクトの生成は、Cocoa クラスの生成メソッドを呼ぶ 79* Cocoa オブジェクトは作りっぱなしでよく、メモリ管理は不要 80 81 82メソッドの返す値 83---------------- 84 85 nstr = OSX::NSString.description 86 p nstr #=> #<OSX::NSCFString:0x1ca90 class='NSCFString' id=0x593200> 87 p nstr.to_s #=> "NSString" 88 89 nstr = OSX::NSString.stringWithString 'Hello world!' 90 p nstr # => #<OSX::NSCFString:0x1c9c8 class='NSCFString' id=0x593400 91 p nstr.to_s # => "Hello world!" 92 93 nstr = OSX::NSString.stringWithString(`pwd`.chop) 94 nary = nstr.pathComponents 95 p nary # => #<OSX::NSCFArray:0x1c8ec class='NSCFArray' id=0x593660> 96 97 ary = nary.to_a 98 p ary # => [#<OSX::NSCFString:0x1c216 class='NSCFString' id=0x593a10>, ...] 99 100 ary.map! {|i| i.to_s } 101 p ary # => ["/", "Users", "hisa", "src", "ruby", "osxobjc"] 102 103これらの例から推測できるように、RubyCocoa では NSString や NSArray などの Objective-C オブジェクトを返すメソッドを Cocoa オブジェクトとして返します。 104積極的に対応する Ruby のオブジェクト (例えば String や Array) には変換しません。 105文字列と配列に関しては、to_s や to_a が定義されているので、それを使って変換できます。 106 107 108メソッド名の決定方法 (1) 109------------------------ 110 111 files = Dir['/System/Library/Sounds/*.aiff'] 112 files.each do |file| 113 snd = OSX::NSSound.alloc 114 snd = snd.initWithContentsOfFile_byReference(file, true) 115 snd.play 116 sleep 0.25 while snd.isPlaying? 117 end 118 119上の例は、さきほど示した音を鳴らす例の別バージョンです。 120Objective-C のメッセージセレクタと引数を Ruby 風に表記する別の方法を示しています。 121 122RubyCocoa では、 123 124 [obj control:a textview:b doCommandBySelector:c]; 125 126に対応する、いくつかの呼び出し方法が用意されています。 127 128基本は、メッセージセレクタの ':' を '_' に置き換えたものが Ruby 側でのメソッド名となります。 129 130 obj.control_textview_doCommandBySelector_(a, b, c) 131 132ただし、最後の '_' は省略することができるので、 133 134 obj.control_textview_doCommandBySelector(a, b, c) 135 136このように書くことができます。 137 138BOOL を返すメソッドの場合には、メソッド名の最後に '?' を付けてください。 139RubyCocoa では、'?' の有無でメソッドが論理値を返すものかどうか判断しています。 140付けない場合には、Objective-C が返した数値 (0:NO, 1:YES) が返りますが、これらの値は Ruby の論理値としてはどちらも真になるので注意してください。 141 142 nary = OSX::MyArray.alloc.init 143 p nary.contains("hoge") # => 0 144 p nary.contains?("hoge") # => false 145 nary.add("hoge") 146 p nary.contains("hoge") # => 1 147 p nary.contains?("hoge") # => true 148 149ただし、AppKit などのあらかじめ BridgeSupport にすべてのメソッド定義が登録されているフレームワークを使う場合には、末尾に '?' がなくても論理値を返すかどうか判断できるため、末尾の '?' は不要です。 150自分で定義したクラスの場合には、メソッド定義は BridgeSupport に登録されていないため、末尾に '?' をつけることが必要です。 151 152 153メソッド名の決定方法 (2) - objc_send 154------------------------------------ 155 156長いメソッド名になると、メッセージセレクタのキーワードと引数の対応関係がわかりにくくなりがちです。 157例えば、NSWindowの初期化は以下のようになります。 158 159 OSX::NSWindow.alloc.initWithContentRect_styleMask_backing_defer( 160 frame, 161 NSTitledWindowMask + NSResizableWindowMask, 162 NSBackingStoreBuffered, 163 false) 164 165このような場合、objc_send を使って可読性を高めることができます。 166 167 OSX::NSWindow.alloc. 168 objc_send(:initWithContentRect, frame, 169 :styleMask, NSTitledWindowMask + NSResizableWindowMask, 170 :backing, NSBackingStoreBuffered, 171 :defer, false) 172 173 174 175メソッドの引数は可能な限り変換する 176---------------------------------- 177 178 OSX::NSString.stringWithString 'hello' 179 180のように、引数の値として Objective-C オブジェクトを取るメソッドを呼び出す場合には、Ruby オブジェクトをそのまま渡しても、出来る限り変換を試みます。 181 182 183メソッド名が重複するときに使う接頭辞 "oc_" 184------------------------------------------ 185 186 klass = OSX::NSObject.class 187 p klass #=> Class 188 klass = OSX::NSObject.oc_class 189 p klass #=> OSX::NSObject 190 191Object#class のように、Ruby と Objective-C でメソッド名 (セレクタ) が全く同じ場合には、Ruby のメソッドが呼ばれます。このような場合には、メソッド名の頭に "oc_" という接頭辞をつけると、Objective-C オブジェクトに対してメッセージが送られます。 192 193 194setterとしての"=" 195----------------- 196 197NSSlider#setMaxValue のようなsetterは、"="でセットすることができます。 198 199 sldr = NSSlider.alloc.init 200 p sldr.maxValue # => 1.0 201 sldr.maxValue = 100 202 p sldr.maxValue # => 100.0 203 204 205Cocoa派生クラス 206--------------- 207 208ここまでは、既存の Cocoa クラスとそのインスタンスを使う方法を説明してきました。 209ここからは、RubyCocoa アプリケーションを書く場合に必要となる、Cocoa 派生クラスの定義やそのインスタンスに関するトピックを扱います。 210Cocoa の派生クラスはややトリッキーな実装により実現しているため、多少の制約や癖がありますが、それも含めて見ていくことにしましょう。 211 212 213Cocoa派生クラスの定義 214---------------------- 215 216RubyCocoa における Cocoa の派生クラスの定義は、通常の Ruby での派生クラス定義と同様に書けます。 217 218 class MyController < OSX::NSObject 219 ib_outlets :messageField 220 221 def awakeFromNib 222 @messageField.stringValue = '' 223 end 224 225 ib_action :greeting do |sender| 226 @messageField.stringValue = 'Merry Christmas!' 227 end 228 end 229 230 231アウトレットの宣言 232------------------ 233 234アウトレットの宣言には ib_outlets (もしくはib_outlet)を使います。 235 236 237アクションの定義・宣言 238---------------------- 239 240アクションの定義には ib_action を使います。 241 242 ib_action :buttonClicked do |sender| 243 ... 244 end 245 246Rubyのメソッドとして定義し、ib_action宣言することもできます。 247 248 ib_action :buttonClicked 249 def buttonClicked(sender) 250 ... 251 end 252 253 254メソッドのオーバーライド 255------------------------ 256 257親クラスで定義されているメソッドをオーバーライドする場合でも、通常の Ruby のメソッド定義をするだけでオーバーライドできます。 258 259 class MyCustomView < OSX::NSView 260 def drawRect(frame) 261 super_drawRect(frame) 262 end 263 end 264 265オーバーライドしたメソッドの中でスーパークラスの同じメソッドを呼ぶ場合には、メソッド名に "super_" という接頭辞をつけて呼びます。 266 267 268Cocoa 派生クラスのインスタンス生成 269---------------------------------- 270 271Cocoa 派生クラスのインスタンスを Ruby プログラムの中で生成する場合には、既存の Cocoa クラスの場合と同様に、 272 273 AppController.alloc.init 274 275のように書きます。Ruby でインスタンスを生成するときのクラスメソッドnewは使えません。 276これにはいろいろな事情があるのですが、ここでは詳しい説明は省略します。 277この制約は、インスタンス生成が、 278 279* alloc (Objective-C 側) 280* alloc 内で Ruby オブジェクト生成 (ここで initialize が呼ばれる) 281 282という順番で行われることと深い関連があります。 283 284 285インスタンス生成時の初期化コードはどこに書くべきか? 286---------------------------------------------------- 287 288一般に Ruby では initialize メソッドに初期化のコードを書きますが、Cocoa 派生クラスではあまり勧められません。 289理由は先に述べたように、インスタンス生成時の initialize メソッドが呼ばれた時点では、Cocoa オブジェクトとしてはメモリが割り当てられただけで初期化が完了していないからです。 290もっとも、Cocoa 側のメソッドを呼ばない限りにおいては、特に問題は発生しないと考えられます。 291 292nib ファイルからロードされるような場合には、awakeFromNib メソッドで初期化するのがもっとも無難です。 293実際に、Cocoa の派生クラスを定義する必要があるのも、このケースがもっとも多いのではないでしょうか。 294 295その他の場合には、Cocoa の流儀で "init" 接頭辞を持つメソッドに書くのがよいでしょう。 296メソッドが self を返すようにすることを忘れないでください。 297