blog.tmp.tokyo

No Promises Await at Journey's End

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

いきなり 2 からスタートですが,1 はこの記事のつもりです。

tomohiko37-i.hatenablog.jp

今朝,6:00 に寝て 8:30 頃にチャイムが鳴ったので誰か来たのかと思ったら郵便局の人が荷物を運んできました。

tomohiko37-i.hatenablog.jp

なんか受け取った時の記憶がほぼないまま昼まで爆睡しました。昼に目が覚めてカップラーメンを食べた後に,昨晩やったアプリを次のステップに進めようと mruby の書籍を購入しました。

これが開発者のまつもとゆきひろ氏の書かれた書籍。何かの連載の集約みたいです。結果的にこれが一番役に立ったかもしれません。

これも買ってみましたが,正直ちょっとベクトルが違った。

これも同じ。今回やりたいこととはちょっと違いました。

Kindle 版です。ハードは持っていませんが,iPhone はアプリを使用すれば Kindle電子書籍が読めます。これで結構 Kindle電子書籍を購入しています。なかなか便利ですので。

さて,今回は何をしようかというと,最終的に画面上で自由にコーディングした上で,そのコードを iPhone 内で実行して結果を表示するということがやりたいのです。そのためには,昨晩書いたコードではちょっと問題があります。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    const char* rubyCode = "puts 'Hello, world.'";
    
    mrb_state* mrb = mrb_open();
    mrb_load_string(mrb, rubyCode);
}

これです。これは実行すると Xcode 上のコンソールに表示してくれるので,rubyCode 変数の内容を画面上に作ったテキストフィールドから受け取るようにすればいいのですが,出力が問題です。Xcode 上のコンソールに表示されてもアプリでは見ることができません。最終的に画面に表示しなくては意味がないのでどうしたものかと悩むこと数時間。こんなコードを書いてみました。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSString *tmpPath = NSTemporaryDirectory();
    NSString *stdoutPath = [tmpPath stringByAppendingString:@"/result.txt"];
    FILE* fp_out = freopen([stdoutPath UTF8String], "w", stdout);
    
    // 実行する Ruby のコード
    const char* rubyCode = "puts 'mruby Hello, world!!'";
    
    // mrb_state の初期化
    mrb_state* mrb = mrb_open();
    
    // コードの実行
    mrb_load_string(mrb, rubyCode);
    
    // mrb_state のクローズ
    mrb_close(mrb);
    
    fclose(fp_out);
    
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:stdoutPath];
    if (!fileHandle) {
        NSLog(@"ファイルがありません.");
        return;
    }
    
    // ファイルの末尾まで読み込む
    NSData *data = [fileHandle readDataToEndOfFile];
    NSString *str= [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(str);
}

もう C 言語と Objective-C がちゃんぽんになってますが,標準出力を制御する方法がよくわからなくてこれにしました。C 言語やってたときに,

FILE* fp_out = freopen("filename.txt", "w", stdout);

とやって標準出力をファイルに落としていたので,これやっちゃえ,と。とは言うものの好き勝手な場所にファイルを吐くのもどうかと思うので,一時ファイルはテンポラリに吐くのがマナー。そこで,iPhone のテンポラリの場所を取るには・・・と思って調べたら,

NSString *tmpPath = NSTemporaryDirectory();

こんな感じ。ここでまた面倒ごとが。標準出力をファイルに切り替えるのはさりげなく "filename.txt" とか書いていますが,C 言語上は char* です。なので NSString から char* に変換しないと。

NSString *str= [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

これで。ちょこっと変換したいだけなのに結構長い記述が必要ですね。ともかく,これで標準出力は内部のファイルに落ちるようになったので,mrb_load_string(mrb, rubyCode); の出力はファイルに行ってくれるはずです。あとは,実行したあとにテンポラリファイルから出力結果を読み込んであげればよろしい。

NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:stdoutPath];
if (!fileHandle) {
    NSLog(@"ファイルがありません.");
    return;
}
    
// ファイルの末尾まで読み込む
NSData *data = [fileHandle readDataToEndOfFile];
NSString *str= [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(str);

読み込んで,その結果をとりあえず NSLogs() で出力してみました。こちらは Objective-C で普通にファイル読み込みをするやり方でいきました。これで無事 str に出力結果を取れたので,画面上に貼り付けることは可能です。

本日はここまで。次回は画面上に入出力のコンポーネントを作ります。