RoboVMまとめ

# RoboVMまとめ

1年くらい[RoboVM](http://robovm.com/)使ってきたので、このへんで得られた知見をまとめときます。

RoboVMに関するオフィシャルな情報は http://docs.robovm.com/ を参照してください。

## RoboVMとは

詳細は http://robovm.com/ を見てください。

JVMのバイトコードをネイティブコードにコンパイルするコンパイラおよびランタイムです。
Mac OS X, iOS, Linux 向けにビルドすることができます。

*Windowsはサポートされていません。また、iOS向けにビルドするにはMacが必要です。*

実際はiOSアプリをJava(およびJVMバイトコードにコンパイルできる言語)で開発するためのツール、といった位置づけになっていますが、単にコマンドラインツールを作ったりすることもできます。

Androidとのクロスプラットフォーム開発を行いやすくするため、ランタイムにはAndroidで標準で利用できるライブラリ(ただしAndroidプラットフォームに依存しないもの)と同等のものが含まれています。

## しくみ

大まかな仕組みについては若干古い内容ですが以下がまとまってて良いです。

[RoboVM 1.0 Released – Commercial Licenses & A Look under the Hood](http://robovm.com/robovm-1-0-released-commercial-licenses-a-look-under-the-hood/)

コンパイラのコアの部分では [Soot](http://sable.github.io/soot/) を使ってJVMバイトコードを一度Jimpleコードという中間表現に変換し、いくつかの最適化と変形を行い、LLVM IRに変換しています。

## とりあえず動かす

まずは愚直に動かしてみましょう。

http://download.robovm.org/ から最新の安定版をダウンロードしてください(robovm-\*.\*.\*.tar.gz)。

展開したディレクトリ以下の `bin/robovm` が実行コマンドになります。

以下のような内容のJavaプログラムを通常通りjavaコンパイラでコンパイルした `Hello.class` ファイルを用意します。

```java
public class Hello {
    public static void main(String[] argv) {
        System.out.println("Hello RoboVM");
    }
}
```

このclassファイル(ソースコードではなくバイトコード)をrobovmでネイティブコードにコンパイルして実行するには以下のように行います。

```sh
robovm -classpath . -run Hello
```

初回はランタイム全体のコンパイルが入るためやや時間がかかりますが、正常にコンパイルができれば `Hello RoboVM` と出力されます。

## とりあえずiOSシミュレータで動かす

今度はiOSアプリケーションとして、シミュレータ上で動かしてみましょう。

iOSアプリとして動かす場合、以下のようなApplecationDelegateを実装したクラスを作る必要があります。

```java
import org.robovm.apple.coregraphics.CGRect;
import org.robovm.apple.foundation.NSAutoreleasePool;
import org.robovm.apple.uikit.NSTextAlignment;
import org.robovm.apple.uikit.UIApplication;
import org.robovm.apple.uikit.UIApplicationDelegateAdapter;
import org.robovm.apple.uikit.UIApplicationLaunchOptions;
import org.robovm.apple.uikit.UIColor;
import org.robovm.apple.uikit.UILabel;
import org.robovm.apple.uikit.UIScreen;
import org.robovm.apple.uikit.UIWindow;

public class HelloIOS extends UIApplicationDelegateAdapter {

    @Override
    public boolean didFinishLaunching(UIApplication application, UIApplicationLaunchOptions launchOptions) {
        final UILabel label = new UILabel(new CGRect(0, 0, 200, 40));
        label.setText("Hello RoboVM");
        label.setTextColor(UIColor.white());
        label.setTextAlignment(NSTextAlignment.Center);

        UIWindow window = new UIWindow(UIScreen.getMainScreen().getBounds());
        window.addSubview(label);
        label.setCenter(window.getCenter());
        window.makeKeyAndVisible();

        return true;
    }

    public static void main(String[] args) {
        try (NSAutoreleasePool ignored = new NSAutoreleasePool()) {
            UIApplication.main(args, null, HelloIOS.class);
        }
    }
}
```

注目すべきは `UILabel`, `UIWindow` などCocoa Touchに含まれるクラス(通常はObjective-CやSwiftで利用するクラス)をJavaから直接利用している点です。

Cocoa Touchに含まれる各種APIをJava風に呼び出せるように、RoboVMはそれらをバインディングしたJavaクラスを提供しています。
Cocoa Touch標準のAPIはほぼすべてバインディングが用意されているので、上記のようにすぐ利用できます。

ではiOSシミュレータで動かしましょう。

まず、通常のJava classファイルとしてコンパイルします。
今回はCocoa Touchバインディングを利用しているため、展開したrobovmのディレクトリ内の `lib` 以下にあるいくつかのjarファイルをクラスパスに含める必要があります。

```sh
javac -cp robovm-rt.jar:robovm-objc.jar:robovm-cocoatouch.jar:. HelloIOS.java
```

次にコンパイルしたクラスファイルをiOSシミュレータ向けにビルドして、実行します。
引数で対象OSをiOS, CPUアーキテクチャをx86_64として指定してください。

```sh
robovm -cp robovm-objc.jar:robovm-cocoatouch.jar:. -os ios -arch x86_64 -run HelloIOS
```

シミュレータが起動してその中で作成したアプリケーションが実行されます。

## 設定ファイルを利用する

毎回コマンドラインでクラスパスなどを指定する代わりに、設定ファイルに書き出してその設定ファイルを指定することもできます。

例えば上記のシミュレータ実行時のコマンドで指定しているオプションは以下の設定ファイルと同等のものです。

```xml
<config>
  <mainClass>HelloIOS</mainClass>
  <os>ios</os>
  <arch>x86_64</arch>
  <classpath>
    <classpathentry>robovm-objc.jar</classpathentry>
    <classpathentry>robovm-cocoatouch.jar</classpathentry>
    <classpathentry>.</classpathentry>
  </classpath>
</config>
```

この設定ファイルは以下のようにしてrobovmコマンドで生成することができます。

```sh
robovm -cp robovm-objc.jar:robovm-cocoatouch.jar:. -os ios -arch x86_64 -dumpconfig robovm.xml HelloIOS
```

上記コマンドでは `robovm.xml` というファイル名で設定ファイルが生成されます。
この設定ファイルを利用して実行する際は以下のように実行します。

```sh
robovm -config robovm.xml -run
```

## Gradleプラグインを利用する

GradleからRoboVMを利用するためにプラグインが利用できます。

https://github.com/robovm/robovm-gradle-plugin

前述のiOSシミュレータの例をgradleプラグインを利用して実行してみましょう。

まず以下の内容で `build.gradle` ファイルを作成します。

```groovy
apply plugin: "java"
apply plugin: "robovm"

buildscript {
  project.ext.roboVMVersion = '1.5.0'

  repositories {
    mavenCentral()
  }
  dependencies {
    classpath "org.robovm:robovm-gradle-plugin:${roboVMVersion}"
  }
}

sourceSets.main.java.srcDirs = ['src']

repositories {
  mavenCentral()
  maven { url "https://oss.sonatype.org/content/repositories/releases/" }
}

dependencies {
  compile "org.robovm:robovm-rt:${roboVMVersion}"
  compile "org.robovm:robovm-cocoatouch:${roboVMVersion}"
}
```

ここでは利用するRoboVMのバージョンを1.5.0としていますが、適宜書き換えてください。

作成した `build.gradle` と前述のRoboVM設定ファイル `robovm.xml` およびソースコードを以下のように配置します。

```
.
├── build.gradle
├── robovm.xml
└── src
    └── HelloIOS.java
```

この状態で以下のgradleタスクを実行すればシミュレータ上での実行ができます。

```sh
gradle assemble launchIPhoneSimulator
```

他にも実機デプロイ、IPA作成、バイナリ作成などいろいろタスクがあるので、一度 [README](https://github.com/robovm/robovm-gradle-plugin/blob/master/README.md) を見てみると良いです。

# RoboVM Studioを利用する

IntelliJ IDEA Community EditionをベースにRoboVM Studioという開発環境も提供されています。

http://download.robovm.org/archives/robovm-studio/1.5.0/robovm-studio-1.5.0.dmg

新規プロジェクト作成時のテンプレートを利用できたり、利用するプロビジョニングプロファイルの選択や、RoboVMのライセンスキーの管理などがGUIから行えます。

# 有償の機能

一部機能は有償で提供されています。

http://robovm.com/pricing/

現状ではInterface Builderの統合とデバッガが有償の機能となっています。トライアル期間もあります。

# バインディング作成(ネイティブコードとの連携)

ネイティブコードとの連携には **Bro** という専用のAPIを使います。
詳細は http://docs.robovm.com/advanced-topics/bro.html にあります。
https://github.com/BlueRiverInteractive/robovm-ios-bindings/blob/master/README.md も参考になります。

サードパーティライブラリを利用する際など、Objective-Cのクラスを使いたくなるときがよくあります。

ここでは [Reachability](https://developer.apple.com/library/ios/samplecode/Reachability/Introduction/Intro.html) を例にバインディングを作成してみましょう。

バインディングはヘッダファイル(ここでは `Reachability.h` )を参考に作成していきます。

Reachability.h は以下のようになっています。(コメントとenum定義を取り除いています)


```objective-c
@interface Reachability : NSObject

+ (instancetype)reachabilityWithHostName:(NSString *)hostName;

+ (instancetype)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress;

+ (instancetype)reachabilityForInternetConnection;

+ (instancetype)reachabilityForLocalWiFi;

- (BOOL)startNotifier;

- (void)stopNotifier;

- (NetworkStatus)currentReachabilityStatus;

- (BOOL)connectionRequired;

@end
```

上記に対応するバインディングは以下のようになります。
(`reachabilityWithAddress:` のバインディングは `sockaddr\_inPtr` がパッケージプライベートなのでここではコメントアウトしています)

```java
@Library(Library.INTERNAL)
@NativeClass
public class Reachability extends NSObject {

    @Method(selector = "reachabilityWithHostName:")
    public static native Reachability createReachability(String hostName);

//    @Method(selector = "reachabilityWithAddress:")
//    public static native Reachability createReachability(sockaddr_inPtr hostAddr);

    @Method(selector = "reachabilityForInternetConnection")
    public static native Reachability createReachabilityForInternetConnection();

    @Method(selector = "reachabilityForLocalWiFi")
    public static native Reachability createReachabilityForLocalWiFi();

    @Method
    public native boolean startNotifier();

    @Method
    public native void stopNotifier();

    @Method(selector = "currentReachabilityStatus")
    public native int getCurrentReachabilityStatus();

    @Method(selector = "connectionRequired")
    public native boolean isConnectionRequired();

}
```

アノテーションをつけるだけで基本的にObjective-CのクラスをそのままJavaに翻訳するようなノリでバインディングできます。
`NSObject` を継承するのも、インスタンスメソッド・クラスメソッドの扱いも、そのままJavaのクラスで同じようにやるだけです。

`@Method` の引数 `selector` にバインディングするメソッド名を書きますが、省略するとメソッド名と同名のセレクタのバインディングになります。
(RoboVMバインディング作成時の慣例として、Javaライクなメソッド名にする慣習があるので、ここではそれに合わせています。)

引数・戻り値の `NSString` は `String` として記述することが可能です。

ビルド時にリンクの指定を追加する必要もあります。リンクするライブラリは `robovm.xml` で `<libs>` 要素で指定していきます。
前提として、ダウンロードしたReachabilityをスタティックライブラリとしてビルドして "libReachability.a" として配置されているとします。


```xml
<config>
  <mainClass>HelloIOS</mainClass>
  <os>ios</os>
  <arch>x86_64</arch>
  <classpath>
    <classpathentry>robovm-objc.jar</classpathentry>
    <classpathentry>robovm-cocoatouch.jar</classpathentry>
    <classpathentry>.</classpathentry>
  </classpath>
  <libs>
    <lib>libReachability.a</lib>
  </libs>
</config>
```