Rakontanto de ludo

Peace cannot be kept by force. It can only be achieved by understanding.

iPhone で Ruby がコーディング & 実行できるアプリを作る!(4)

こんばんわ。iPhone アプリの開発が面白くなって他のことに手がつきません。今日もアプリ開発の話です。

TSUTAYA DISCAS からメールが来ました。ラブライブ 2nd Season の 3 巻と 4 巻が発送されました。予定通り明日は視聴予定です。明日は「見終わった」というネタになる予感。

前回のラブライブ!

いい加減これでもかという見出しにしていますが,要するに前回のおさらいです。

tomohiko37-i.hatenablog.jp

というわけで mrubyiOS 向けにビルドして Objective-C 内でほとんど C 言語みたいな記述で Ruby のコードを実行する処理を実現し,簡単な GUI 画面を作成して画面上から入力した Ruby のコードを実行して標準出力を捕まえて結果を表示するというほぼ目的を達成したアプリを作り,自分の iPhone 6+ で動かすところまでやりました。

実はこれで目的を達成しましたのでこのシリーズもおしまいです,としたいところですが AppStore でリリースできるくらいまではブラッシュアップしようかと思います。それが今回です。

アプリの名前を決めた

アプリの名前を mruby.tmp とすることにしました。

画面を遷移する

ちょっと画面遷移をしてみようかと思い segue を使った画面遷移を作ってみることにしました。要するに Ruby のコードを入力する画面と結果を表示する画面を分離しようというわけです。Interface Builder で StoryBoard を編集しました。

作った 2 つの画面の構成はこんな感じ。

f:id:tomohiko37_i:20150804230649p:plain

最初の画面の実行ボタンから segue をひっぱり,次の画面へつなげます。

f:id:tomohiko37_i:20150804230716p:plain

細かいことは抜きにして,前画面に紐付いている ViewController クラスに以下のような処理を書いてます。前回のコードからはだいぶ変わりました。原型を留めていないかもしれません。

//
// テキストフィールドやボタン以外の場所がタップされた時の処理
//
- (IBAction)onSingleTap:(UITapGestureRecognizer *)sender {
    
    // 表示されているキーボードを閉じる
    [self.view endEditing:YES];
}

//
// クリアボタンをタップした時の処理
//
- (IBAction)clearTextField:(UIButton *)sender {
    
    // ソースの内容をクリア
    self.srcCodeTextField.text = @"";
}

//
// セグエで遷移した次画面から戻って来る際の処理.
//
- (IBAction)goBackHome:(UIStoryboardSegue *)segue {
    // 実装処理なし
}

//
// segue で遷移する直前に動作する処理.
//
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    
    // Ruby コードの実行を行う
    [self execRubyCode];
    
    // 次画面のオブジェクトを取得して値を引き継ぐ
    ResultViewController *resultViewController = segue.destinationViewController;
    resultViewController.result = self.result;
}

//
// Ruby のコードを実行する処理.
//
-(void)execRubyCode {
    
    // 現状,標準出力へ出力される情報(mruby の実行結果など)を捕まえる手段が
    // 見つかっていないので C 言語でやるようにファイルに向き先を切り替えて
    // 標準出力の内容をファイル読み込みによる取得を行っている.
    NSString *tmpPath = NSTemporaryDirectory();
    NSString *stdoutPath = [tmpPath stringByAppendingString:@"/stdout.txt"];
    NSString *stderrPath = [tmpPath stringByAppendingString:@"/stderr.txt"];
    
    FILE* fp_out = freopen([stdoutPath UTF8String], "w", stdout);
    FILE* fp_err = freopen([stderrPath UTF8String], "w", stderr);
    
    // 実行する Ruby のコード
    const char* rubyCode = [self.srcCodeTextField.text UTF8String];
    
    // mrb_state の初期化
    mrb_state* mrb = mrb_open();
    
    // コードの実行
    mrb_load_string(mrb, rubyCode);
    
    // mrb_state のクローズ
    mrb_close(mrb);
    
    fclose(fp_out);
    fclose(fp_err);
    
    NSFileHandle *stdoutFileHandle = [NSFileHandle fileHandleForReadingAtPath:stdoutPath];
    NSFileHandle *stderrFileHandle = [NSFileHandle fileHandleForReadingAtPath:stderrPath];
    if (!stdoutFileHandle) {
        self.result = @"標準出力情報なし";
        return;
    }
    
    if (!stderrFileHandle) {
        self.result = @"標準エラー情報なし";
        return;
    }
    
    // ファイルの末尾まで読み込む
    NSData *stdoutData = [stdoutFileHandle readDataToEndOfFile];
    NSData *stderrData = [stderrFileHandle readDataToEndOfFile];
    NSString *stdoutStr = [[NSString alloc] initWithData:stdoutData encoding:NSUTF8StringEncoding];
    NSString *stderrStr = [[NSString alloc] initWithData:stderrData encoding:NSUTF8StringEncoding];
    
    self.result = [stdoutStr stringByAppendingString:stderrStr];
}

ポイントとしては,ボタンタップで Action メソッドを実行するようにしていたところを segue による画面遷移したために segue で遷移する直前に動く prepareForSegue:sender: メソッドをトリガーにしている点です。ここで画面上のテキストフィールドから取得した Ruby コードを mruby を使って実行します。この辺りは前回の内容とまったく一緒。処理の大部分を execRubyCode: メソッドへ分離しているのは,prepareForSegue:sender: のようなイベント系のメソッドiOS が提供するものなので,iOS のバージョンアップで突然非推奨になったりすることがあります。実際に iOS7 から iOS8 になったときに非推奨になったメソッドなどはたくさんあったので,この手のメソッドにいろいろ書いてしまうといざ使えなくなった時に涙目になるのです。

それと,

// 次画面のオブジェクトを取得して値を引き継ぐ
ResultViewController *resultViewController = segue.destinationViewController;
resultViewController.result = self.result;

この方法は調べると結構出てきます。segue を使って画面遷移をする際に次画面へ値を引き渡す方法です。簡単に言うと segue.destinationViewController: で次の画面のインスタンスを取得しておいて,次画面のインスタンスに対して値をセットしておく,ということ。仕組みがわかればたいしたことはしていないです。

動きを録画した

というわけで,実装した内容を実機で動かしましたので録画してみました。iPhoneiPad の動きを録画するのは実は簡単です。iPhonemac につないで QuickTime を起動すると録画できます。YouTube にアップしました。

youtu.be

まとめ

以上,本日やった内容です。画面遷移を実装してちょっとアプリっぽくなりました。いろいろ Ruby のコードを試してみようと思います。実はエラーだったときに情報が出せていないので,その辺はきちんと例外処理するように実装したいのですが,それよりも次は書いたコードの保存と読み込みになりますね。これができれば Ruby のコードをたくさん書いても任意に実行できるので簡単なスクリプト処理を書き溜めることができます。

それでは今回はこの辺で。