Tiger+Xcode2.xな人が「RubyCocoa入門」するためのHello worldチュートリアルっぽいものを書いた

RubyCocoaチュートリアルは以下のサイトにあるが、自分の環境が古い(Mac OS X 10.4.11 + Xcode2.5)せいで、この手順ではアプリケーションを作成することができずあちこち調べ回るはめになった。

大きく違っているところはXcode3.xのInterfaceBuilderではRubyのクラスで定義されてているoutletやactionを自動的に認識してnibファイルに同期が行われるっぽいが、Xcode2.xではそのへんの同期を手作業で行う必要があるみたい。そのへんの情報は以下のページに記載されていた。

多分最新の環境(Leopard)であればここまでハマらずにすんなり行けたはず。とりあえず Tiger + Xcode2.5 な環境でRubyCocoaに入門しようとしている人のために、Hello world するまでの手順を紹介してみる。

プロジェクトの作成

まず Xcodeを起動して

ファイルメニューから「新規プロジェクト」を選択

新規プロジェクトから”RubyCocoa”を選択し

適当にプロジェクト名とプロジェクトの格納先を決める

格納先のフォルダが無い場合は作成するか聞いてくるので”はい”を選択

で、プロジェクトができる。

InterfaceBuilderでユーザインターフェース作成

まず、ユーザインターフェースを作成するためInterfaceBuilderを起動する。プロジェクトのファイルの一覧の中に MainMenu.nib というファイルがあるはずなので、それをダブルクリックするとInterface Builderが起動する。

InterFaceBuilderが起動すると、真っ白で何も部品が配置されていないウィンドウが表示される。これが、新規に作成するアプリケーションのウィンドウで、これに各種部品を配置してユーザインターフェースを構築することになる。

詳細は省くが、NSButton, NSTextField を適当に配置して以下のようなユーザインターフェースを作成する。

以上でユーザインターフェースが完成。次はクラスの設計に入る。

InterfaceBuilderでコントローラクラス設計

Objective-C + Xcode2.x での開発はクラスの設計を InterfaceBuilder で行い、その設計を元にクラスのひな形を生成することになる。RubyCocoa + Xcode2.xの場合、最後の「ひな形を生成」ってのは出来ないが、クラスの設計はやはりInterfaceBuilderで行う必要がある。

InterfaceBuilderで作成したユーザインターフェースやクラス設計の情報は nib ファイルとして保存されて、アプリケーション実行時に各オブジェクト同士を関連づけたりするのに使っているっぽい*1

まず、nibウィンドウをClassesに切り替え、NSObjectのサブクラスを新規に作成する。

で、適当なクラス名を付けるが、このクラス名と後述するRubyのクラス名は一致させる必要があることに注意。とりあえず、ここでは AppControllerとしておく。

次に、そのクラスにoutletとactionを作成する。該当クラスのコンテキストメニューから Add outlet to xxx を選択する。

そうすると、インスペクタウィンドウにoutletが追加されるので、適当な名前を付ける。この名前も後述のRubyクラスに定義する ib_outlets の宣言と一致させる必要があるので注意。とりあrず、ここでは text という名前にする。

outletを追加したら、actionsタブを開いて actionを追加する。この名前もRubyクラスと一致させる必要あり。とりあえず say という名前にする。

ちなみに、actionの名前の末尾には自動的に":"が付くが気にせずそのまま進む。

クラスへの outlet と action の定義が完了したら、そのクラスのインスタンスを作成する。

そうすると、nibウィンドウのInstancesタブの中にインスタンス(青い立方体)が追加される

ここで注意点。outeletやactionはクラスのインスタンス変数やインスタンスメソッドに相当するもの。それらの変更を行う場合は nib ウィンドウの Classes タブから該当クラスのAttribute の定義を変更する必要がある。この辺りは慣れるまでちょっとややこしい。

以上で、コントローラクラスの設計が出来たので、次はユーザインターフェース部品とコントローラクラスの関連の定義を行う。

ユーザインターフェース部品とコントローラクラスの接続

InterfaceBuilderでのオブジェクト同士の関係の定義は、画面上でオブジェクトからオブジェクトに線を引っ張ることで行う。直感的で楽しい。

まず、AppControllerからウィンドウのテキストボックスに線を引っ張る*2

すると、どのoutletに接続するか聞いてくるので、さっき定義した "text"をダブルクリック
して選択する。選択されたoutletは頭に灰色の丸印が付くのでわかる。

同じ要領で今度はボタンからAppControllerに線を引っ張る。

すると、今度はどの action に接続するか聞いてくるので、上で定義した "say:"をダブルクリックして選択。

以上でオブジェクト同士の関係の定義が完了、InterfaceBuilderでの作業はここまで。定義した情報を保存しておく。

クラス設計に従いRubyクラス実装

Objective-Cでの開発では、InterfaceBuilderで定義した内容を元にクラスのひな形を生成してくれるが、Tiger+Xcode2.5+RubyCocoaの場合はそうはいかない*3ので自分で作成する必要がある。と、言ってもそんなに難しくはない。

プロジェクトのコンテキストメニューから「追加」ー「新規ファイル」を選択。

ファイルのの種類から、"Ruby NSObject Subclass"を選択。

クラス名を聞いてくるので、InterfaceBuilderで作成したクラスと同じ名前(ここでは AppController)でクラスを作成する。

そうすると、中身が空っぽのクラスのひな形ができる。

この中に、InterfaceBuilderで定義した outlet と action を定義する。それぞれ、ib_outlets と ib_action にシンボルとして宣言する。その名前は InterfaceBuilderで定義したものと一致させる必要があるので注意。

ib_outletsに定義したシンボルは同名のインスタンス変数が作成されRubyクラスの中で@textという感じで参照できる。ib_actionに定義したシンボルと同名メソッドはObjective-Cの世界から呼び出すことが可能になる。*4

で、AppController.rbを以下のように実装する。

require 'osx/cocoa'

class AppController < OSX::NSObject
  ib_outlets :text
  
  def say(sender)
    @text.setStringValue('CocoaRuby world!')
  end
  ib_action :say
end

AppController.rbは最終的に以下のようになる。

以上でクラスの実装が完了。

動作確認

ファイルを保存したら「ビルドして実行」

以下のようなウィンドウが表示されるはず、

で、"Say"ボタンを押すと.. テキストボックスに "RubyCocoa world!"と表示されるはず。

以上で Hello world 終了。

あとは..

ここまで出来ればあとはこれの応用なので「RubyCocoa 入門」もできるはず。

*1:このへんは勉強不足なので、違っている可能性大

*2:Ctrlを押しながらAppcontrollerをクリックしそのままドラッグすると線がのびていくので、その線をテキストボックスにつなげる

*3:どうやらひな形を作成してくれるスクリプトが添付されているらしい。今度使ってみるつもり。

*4:たぶん...このへんちょっと曖昧