書いた人 : 海老沢 聡 (@satococoa)
この記事では 2012 年 5 月に発売された、Ruby で iOS アプリが開発できる RubyMotion を紹介します。
今後購入を考えている個人や導入を検討中の企業の方、買ってはみたけれども次にどうして良いかわからない、という方などの参考になると幸いです。
RubyMotion とは Ruby で iOS アプリを作るための製品です。 開発には使い慣れたテキストエディタ、テストは spec、ビルドは rake コマンドという、Rubyist にとっては普段慣れ親しんだ環境で開発を行うことができます。
MacRuby をベースに開発されていてソースコードの多くが MacRuby と同一だそうです。 RubyMotion 自体はリリースが 2012 年 5 月と非常に若い製品ではありますが、実績のある MacRuby をベースとしているのでその分安心感があります。 また主要開発者である、開発元の HipByte 社創業者の Laurent Sansonetti さんは以前 Apple で MacRuby の開発をされていた方です。
なお RubyMotion は有料であり、お試しプランなどもありません。 RubyMotion の実際に動作しているところを見たい場合は、公式サイトの RubyMotion - Getting Started に掲載されている動画や、 The Pragmatic Studio の RubyMotion Screencast をご覧になるのがおすすめです。 (企業等での多数のライセンスの導入や、学割などを希望される場合は直接サポートに連絡すると優遇を受けられるそうです。)
RubyMotion の良いところは以下の 3 点だと思います。(一番良い点は「慣れ親しんだ Ruby で iOS のアプリが作れる」というただそれだけに尽きると感じていますが。)
逆に気をつけた方がいいのは以下の点です。
RubyMotion Store からライセンスを購入します。支払いはクレジットカード、もしくは PayPal アカウントで購入が可能です。
ライセンスは 1 年間有効で期間中はソフトウェアのアップデートとサポートチケットによるサポートを受けることができます。 アップデートとサポートが不要の場合は一度購入するだけでずっと製品を使い続けることもできます。 なおライセンスの継続料金は新規料金の半額となります。(参照: RubyMotion - Support)
購入が完了するとライセンスキーとインストーラの URL が購入時に登録したメールアドレスあてに送られてきますので、記載された指示に従ってインストールを行ってください。
インストールが完了すると、motionというコマンドが使えるようになります。
また、/Library/RubyMotion以下に必要なファイルがインストールされます。
インストールが完了したら、さっそく簡単なプロジェクトを作ってみたいと思います。 まずはmotionコマンドでコードを生成します。
早速テキストエディタ (エディタ用の拡張等については後述) で app/app_delegate.rb を開いてみます。
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
true
end
end
デフォルトでこのようなコードが書いてあると思います。 2 行目をご覧ください。Ruby 2.0 で取り入れられるという噂のキーワード引数のようなメソッド定義になっています。
これは RubyMotion 独自の拡張ではなく、MacRuby で拡張されたものです。ちなみに MacRuby は CRuby 1.9 系をベースに開発されていて、同様に RubyMotion の処理系も基本的には 1.9 系に準拠しています。
早速実行してみます。
./app/app_delegate.rb がコンパイルされ、iOS アプリとして動作するために必要なファイルが生成されたり、といった一連の処理が行われた後、自動的に iOS シミュレータが起動します。
まだ何も描画するコードを書いていませんので、シミュレータには真っ黒の画面が表示されるのみです。
ここでrakeコマンドを実行した端末の方に注目すると、入力待ちになっています。 実は RubyMotion には REPL が実装されていて、irb のように直接コードを打ち込んで実行することができます。
少し実験してみましょう。プロンプトに続いてコードを入力してみてください。
さて驚きは次です。引き続き端末にコードを打ち込んでください。
なんとクラスツリーにNSMutableStringやNSStringといった Objective-C 由来のクラスが混ざり込んでいます。正確にはこれも RubyMotion というよりは MacRuby の特徴なのですが、実は Ruby コードから Objective-C で定義されているメソッドまで呼び出すことができてしまいます。
試してみましょう。
面白いですね。
では REPL を終了します。
いつまでも真っ黒な画面では寂しいので、app_delegate.rb にコードを記述してみましょう。
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
controller = UIViewController.new
controller.view.backgroundColor = UIColor.whiteColor
@window.rootViewController = controller
@window.makeKeyAndVisible
true
end
end
コードの中身については Objective-C でアプリを開発した経験が無いと理解が難しいかもしれませんが、筆者は Objective-C 経験ほぼゼロの状態で RubyMotion を始めましたので、平行して勉強していくことも十分可能です。 これで先ほど同様にrakeコマンドを実行すると今度は白い背景の画面が表示されるようになったと思います。
では画面にボタンなどの部品を置いてみようと思います。 Storyboard を使うこともできますが、ここではコードのみで GUI を構築します。
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
controller = UIViewController.new
controller.view.backgroundColor = UIColor.whiteColor
label = UILabel.new.tap do |l|
l.text = 'foo'
l.frame = [[10, 10], [100, 60]]
end
controller.view.addSubview(label)
button = UIButton.buttonWithType(UIButtonTypeRoundedRect).tap do |b|
b.setTitle('Say Hello!', forState:UIControlStateNormal)
b.frame = [[110, 300], [100, 60]]
end
controller.view.addSubview(button)
@window.rootViewController = controller
@window.makeKeyAndVisible
true
end
end
UILabelとUIButtonを追加してみました。実行してみると、ラベルの位置が気になりませんか? REPL で位置の調整を試してみましょう。
コマンドキーを押しながらシミュレータ上でポインタを動かすと、赤い枠線がポインタの下の UI 部品を囲み、それと同時に端末上のプロンプトが (main)> から (#<UILabel:0x6a015c0>)> に変わるのが確認できましたでしょうか?(0x6a015c0 は同じ数字ではなくても大丈夫です。)
この状態でクリックすると、REPL が 選択されたオブジェクトをselfとした状態で入力待ちになります。以下のコードを入力してみましょう。
ラベルの位置が変更できたと思います。 何度もコンパイルし直して位置を調整するよりも簡単に UI 部品の位置、大きさの調整ができることを実感できますね。 忘れずに元の app_delegate.rb の 9 行目を今確認したframeの値に変更しておきましょう。
label = UILabel.new.tap do |l|
l.text = 'foo'
l.frame = [[110, 100], [100, 20]]
end
controller.view.addSubview(label)
さて、今はボタンをタップしても何も起きません。 タップするとラベルの文字列が変更されるように修正したいと思いますが、このまま app_delegate.rb にコードを書いていくと複雑になってしまうので、UIViewControllerを継承したMyViewControllerクラスを定義し、ファイルを分けるリファクタリングを行います。
リファクタリング後の 2 ファイルは以下のようになります。 実行して、先ほどと同じ結果になることを確認してください。
# app/app_delegate.rb
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
my_view_controller = MyViewController.new
@window.rootViewController = my_view_controller
@window.makeKeyAndVisible
true
end
end
# app/my_view_controller.rb
class MyViewController < UIViewController
def viewDidLoad
super
view.backgroundColor = UIColor.whiteColor
label = UILabel.new.tap do |l|
l.text = 'foo'
l.frame = [[110, 100], [100, 20]]
end
view.addSubview(label)
button = UIButton.buttonWithType(UIButtonTypeRoundedRect).tap do |b|
b.setTitle('Say Hello!', forState:UIControlStateNormal)
b.frame = [[110, 300], [100, 60]]
end
view.addSubview(button)
end
end
あれ?と思った方もいらっしゃるかもしれません。RubyMotion ではrequireが使えない代わりに、app ディレクトリ以下のファイルは自動的に読み込まれるようになっています。 (もし読み込まれる順序が問題でコンパイルできない場合は Rakefile で app.files_dependencies メソッドを使って指定できます。2.1. Files Dependencies)
ではボタンをタップしたときの動作を追加してみます。 変更点を diff 形式で掲載します。
まず別のメソッドからラベルが見えるようにUILabelのオブジェクトをインスタンス変数に格納するように変更しました。 さらに、say_helloというメソッドを定義し、その中でラベルのプロパティを変更しています。 そしてボタンがタップされたときに呼び出されるメソッドをaddTarget:action:forControlEvents:メソッドで指定して完成です。
実行してみて意図通り動作することを確認してください。
現在の RubyMotion では Bacon という rspec クローンを Objective-C 用に拡張した MacBacon を利用してテストを書くことができます。
ユニットテストのレイヤーから、バージョン 1.15 以降では UIAutomation 相当の View, Controller のテストが書けるようになっています。
エディタで spec/main_spec.rb を開いてください。以下のようなコードが記述されています。
describe "Application 'Hello'" do
before do
@app = UIApplication.sharedApplication
end
it "has one window" do
@app.windows.size.should == 1
end
end
アプリケーションに一つのwindowがあることをテストしています。 実行してみます。
図らずも既に仕様を満たしていたのでテストが通りました。 (筆者はテストが一発で通ることに恐怖を感じるので、一応@app.windows.size.should == 0としてテストが失敗することも確認しました。)
では先ほど作ったプロジェクトのテストを書いてみましょう。
describe "Application 'Hello'" do
before do
@app = UIApplication.sharedApplication
end
it "has one window" do
@app.windows.size.should == 1
end
describe "rootViewController" do
before do
@controller = @app.keyWindow.rootViewController
end
it "is an instance of MyViewController" do
@controller.class.should == MyViewController
end
end
end
コントローラのクラスが正しく MyViewController であることをテストしました。 テストを実行してみてください。
正しく通りましたでしょうか。
では次にボタンをタップしたときの動作を検証します。 spec/my_view_controller_spec.rb を作成し、以下のように記述します。
describe "The 'My View Controller' view" do
tests MyViewController
before do
@label = view('foo')
end
it "change label's title" do
tap('Say Hello!')
@label.text.should == 'Hello'
end
end
実行してみます。
おっと、残念ながらエラーが出てしまいました。 spec の中で typo したようです。修正は皆さんにおまかせします。
RubyMotion で開発を行うにあたって、定番のツールを紹介したいと思います。
iOS SDK のリファレンスを参照するために使います。 Ruby や Rails のリファレンスも参照できる優れものです。
iOS 開発では定番! Adhoc 版アプリを素早く手軽に配布するために使います。
Bundler と同様にライブラリの依存関係の管理を行う CocoaPods を RubyMotion から簡単に使えるようにした gem です。
これのおかげで Objective-C で作られた AFNetworking などの定番ライブラリもとても手軽に使うことができます。
上述の TestFlight を RubyMotion から手軽に使うための gem です。
この gem を利用するとrake testflight notes=”foo”のように、コマンドラインから TestFlight を通じたテスト版の配布ができてしまいます。
RubyMotion を使った iOS 開発をさらに Ruby らしく書けるようにした gem です。
spec の実行結果に色を付ける gem です。
他にも nitron, formotion なども人気の高い gem です。
まだ若い製品であるため、日本語のまとまった情報はまだ多くはありません。 しばらくは英語の情報を主に見るようにするといいでしょう。
先日第1回 RubyMotion 勉強会を東京にて開催いたしました。
その懇親会の席でそろそろ日本語の RubyMotion の情報をどこかに集約した方がいいのでは……という話になり、Github 上に Organization を立ち上げました。(RubyMotionJP)
当面は RubyMotion Tutorial の翻訳を進めていこうと考えております。
やや駆け足ではありますが、これから RubyMotion を始めたいと考える方のスタートダッシュをお手伝いできるよう、広く浅くまとめてみました。
また、簡単なコード例をご覧頂くことで開発の雰囲気や手順、メリット/デメリットをある程度感じていただくことができたのではないかと思います。
何か質問等がございましたらどうぞお気軽に Facebook グループや Twitter の #rubymotionjp ハッシュタグで相談していただけると、拾える範囲で拾って回答を差し上げることができると思います。
RubyMotion は若くて勢いのあるプロジェクトです。バグも相当減ってきた実感がありますし、次々と便利で新しい機能が追加されています。近々デバッガやdefine_methodの追加などの大きなアップデートも控えています。
RubyMotion やこの記事を通じて、多くの方が楽しく iOS アプリの開発ができるようになることを願っています。
海老沢 聡 (@satococoa)
原宿のとある会社でスマートフォンアプリの開発をしています。現在 Rails + RubyMotion でアプリ開発中。
先日 RubyMotion で開発した自身初の iPhone アプリをリリースし、見事 AppStore 無料のトップを頂きました!