RoboVMはGroovyの夢を見るか
はじめに
これはG*アドベントカレンダーの最終日、第25日目の記事です。
巷では「AndroidはGroovyの夢を見るか」という話がありますが、もう一つの雄である、iOSでGroovyは何とかならんのか、というのが今回のネタです。
もう少し具体的に言うと、JavaでiOS用アプリを開発するソフトウェアとしてRoboVMがありますが、Groovyで使えないか?を探ります。
結論から言うと、力及ばず、夢は夢のままでした... (¦3[___]
#私の環境だけかもしれませんが...
この記事では、次の環境で動作をさせています。
OS: OSX 10.9.5 Java: 1.8.0_25 Groovy: 2.4.0-rc-1 Gradle: 2.2.1 XCode: 6.1.1 RoboVM: 1.0.0-beta-01
ここからは、私が何をして、何ができて、何ができなかったのか、第一歩でコケた話をお楽しみください。
RoboVMのインストール
RoboVMのインストール自体は簡単です。RoboVMの「Download the latest RoboVM release」からtar+gzファイルをダウンロードし、適当なディレクトリに展開します。
tar zxvf robovm-1.0.0-beta-01.tar.gz
あとは、展開してできるディレクトリを環境変数ROBOVM_HOMEに、${ROBOVM_HOME}/binを環境変数PATHに、それぞれ設定します。
先人の知恵を参考にする
既に「RoboVMをGroovyで」という先人が何人かいらっしゃるようで、今回はここから一式クローンして試してみます。
git clone https://github.com/msgilligan/GradleGroovyRobot.git
名前からわかるように、Gradleのプロジェクトになっています。build.gradleにちょっと手を入れ、次のように変更しました。
apply plugin: 'groovy' repositories { mavenCentral() flatDir { dirs 'lib' } } def robovmver = "1.0.0-beta-01" project.ext.set("robovmver", robovmver) dependencies { compile 'org.codehaus.groovy:groovy-all:2.4.0-rc-1' compile "org.robovm:robovm-rt:$robovmver" compile "org.robovm:robovm-objc:$robovmver" compile "org.robovm:robovm-cocoatouch:$robovmver" } project.ext.set("robovm_home", "/Users/rhapsody/Tools/RoboVM/robovm-$robovmver") // main class String mainClass = "IOSDemo" def groovyJar = "" task findGroovyJar << { groovyJar = project.configurations.compile.find { it.name.startsWith("groovy-all-") } } // compile bytecode to llvm and run in the ios simulator task run (dependsOn: [build, findGroovyJar]){ doFirst { println(">> Running RoboVM") String cmd = "$project.robovm_home/bin/robovm -verbose -arch x86 -os ios -cp $project.robovm_home/lib/robovm-objc.jar:$project.robovm_home/lib/robovm-cocoatouch.jar:$projectDir/build/classes/main/:$groovyJar -run $mainClass" def proc = cmd.execute() proc.in.eachLine {line -> println line} proc.err.eachLine {line -> System.err.println( 'ERROR: ' + line)} proc.waitFor() } }
サンプルのコード
サンプルのコードですが、クローンしたプロジェクトに含まれていますが、ここに載っているJavaのコードをGroovyで書きなおしてみました。ポイントは、@CompileStaticを使うことです。
import org.robovm.apple.coregraphics.CGRect import org.robovm.apple.foundation.NSAutoreleasePool import org.robovm.apple.uikit.UIApplication import org.robovm.apple.uikit.UIApplicationDelegateAdapter import org.robovm.apple.uikit.UIApplicationLaunchOptions import org.robovm.apple.uikit.UIButton import org.robovm.apple.uikit.UIButtonType import org.robovm.apple.uikit.UIColor import org.robovm.apple.uikit.UIControl import org.robovm.apple.uikit.UIControlState import org.robovm.apple.uikit.UIEvent import org.robovm.apple.uikit.UIScreen import org.robovm.apple.uikit.UIWindow import groovy.transform.CompileStatic @CompileStatic class IOSDemo extends UIApplicationDelegateAdapter { UIWindow window = null int clickCount = 0 @Override boolean didFinishLaunching(UIApplication application, UIApplicationLaunchOptions launchOptions) { final UIButton button = UIButton.create(UIButtonType.RoundedRect) button.frame = new CGRect(115.0f, 121.0f, 91.0f, 37.0f) button.setTitle('Click me!', UIControlState.Normal) /* button.addOnTouchUpInsideListener(new UIControl.OnTouchUpInsideListener() { @Override public void onTouchUpInside(UIControl control, UIEvent event) { button.setTitle('Click #' + (++clickCount), UIControlState.Normal) } }) */ window = new UIWindow(UIScreen.mainScreen.bounds) window.backgroundColor = UIColor.lightGray() window.addSubview(button) window.makeKeyAndVisible() true } static void main(String[] args) { NSAutoreleasePool pool = new NSAutoreleasePool() UIApplication.main(args, null, IOSDemo) pool.close() } }
これで実行の準備が整いました。では実行してみましょう。
gradle run
初回はバイトコードをネイティブなコードに変換しているようで時間がかかり、また私の環境だけかもしれませんが、エラーでiOSシミュレータが起動しなかったりする場合があります。そのような場合は、再度実行すると、次のようにiOSシミュレータが起動し、アプリが実行されます。
ここまではできるのですが、ここから先には進めませんでした...orz
サンプルのコード、その2
先程のサンプルコードですが、コメントアウトしている箇所があります。
button.addOnTouchUpInsideListener(new UIControl.OnTouchUpInsideListener() { @Override public void onTouchUpInside(UIControl control, UIEvent event) { button.setTitle('Click #' + (++clickCount), UIControlState.Normal) } })
ボタンにリスナーを登録して、ボタンがクリックされた場合に、ボタンのタイトルを再設定することをしています。
では、このコメントアウトしている箇所を有効にして実行してみましょう。iOSシミュレータが起動しますが、先程のようにアプリは実行されず、次のようなメッセージが出力されてしまいます。
#ちなみに、クローンしたプロジェクトのサンプルコードも基本的に同じで、リスナーを登録するコードはありませんでした。
[WARN] android.System: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks. [WARN] android.System: java.lang.Throwable: Explicit termination method 'close' not called at dalvik.system.CloseGuard.open(CloseGuard.java) at java.io.RandomAccessFile.<init>(RandomAccessFile.java) at java.io.RandomAccessFile.<init>(RandomAccessFile.java) at java.util.zip.ZipFile.<init>(ZipFile.java) at java.util.zip.ZipFile.<init>(ZipFile.java) at java.lang.PathClassLoader.init(PathClassLoader.java) at java.lang.PathClassLoader.findResource(PathClassLoader.java) at java.lang.ClassLoader.getResource(ClassLoader.java) at java.lang.ClassLoader.getResourceAsStream(ClassLoader.java) at org.codehaus.groovy.reflection.GeneratedMetaMethod$DgmMethodRecord.loadDgmInfo(GeneratedMetaMethod.java) at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.registerMethods(MetaClassRegistryImpl.java) at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.<init>(MetaClassRegistryImpl.java) at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.<init>(MetaClassRegistryImpl.java) at groovy.lang.GroovySystem.<clinit>(GroovySystem.java) at org.codehaus.groovy.runtime.InvokerHelper.<clinit>(InvokerHelper.java) at groovy.lang.GroovyObjectSupport.<init>(GroovyObjectSupport.java) at groovy.lang.Reference.<init>(Reference.java) at IOSDemo.didFinishLaunching(IOSDemo.groovy) at org.robovm.apple.uikit.UIApplicationDelegate$ObjCProxy.$cb$application$didFinishLaunchingWithOptions$(Unknown Source) at org.robovm.apple.uikit.UIApplication.main(Native Method) at org.robovm.apple.uikit.UIApplication.main(UIApplication.java) at IOSDemo.main(IOSDemo.groovy) [WARN] java.lang.Class: Class.forName() failed to load 'java.lang.ClassValue'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>java.lang.ClassValue</pattern></forceLinkClasses> to your robovm.xml file to link it in. Loading class 'java.util.logging.ConsoleHandler' failed java.lang.ClassNotFoundException: java.util.logging.ConsoleHandler java.lang.ExceptionInInitializerError at org.codehaus.groovy.runtime.InvokerHelper.<clinit>(InvokerHelper.java) at groovy.lang.GroovyObjectSupport.<init>(GroovyObjectSupport.java) at groovy.lang.Reference.<init>(Reference.java) at IOSDemo.didFinishLaunching(IOSDemo.groovy) at org.robovm.apple.uikit.UIApplicationDelegate$ObjCProxy.$cb$application$didFinishLaunchingWithOptions$(Unknown Source) at org.robovm.apple.uikit.UIApplication.main(Native Method) at org.robovm.apple.uikit.UIApplication.main(UIApplication.java) at IOSDemo.main(IOSDemo.groovy) Caused by: java.lang.NullPointerException at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.<init>(MetaClassRegistryImpl.java) at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.<init>(MetaClassRegistryImpl.java) at groovy.lang.GroovySystem.<clinit>(GroovySystem.java) ... 8 more BUILD SUCCESSFUL Total time: 34.537 secs
リスナーが登録できなければ、先に進めません...
時間もないので、今回はここまでです...
最後に
今後、やる気があれば、できたバイトコードをデコンパイルしたり、Groovyのソースを追っかけてみるつもりです。
Android用のコードが入っているので、同じようにRoboVM用のコードを追加してなんとかなるといいのですが。