Xamarin 使いが Kotlin のマルチプラットフォーム対応コードを読んだ感想

Kotlin のマルチプラットフォーム対応、アツいですね。

* [KotlinConf 2017 Keynote レポート | TechBooster](https://techbooster.org/android/17997/)
* [JetBrains/kotlinconf-app: KotlinConf Schedule Application](https://github.com/JetBrains/kotlinconf-app)

上の kotlinconf-app の対応プラットフォームは、

* サーバーサイド（Kotlin for Server-side, Ktor）
* Webページ(Kotlin/JS, React) 
* Android(Kotlin/JVM)
* iOS(Kotlin/Native)

となっています、すご！

## Xamarin と比べてどうよ？

普段 Xamarin を使用して Android/iOS アプリを開発しているので、クロスプラットフォームアプリ開発技術が増えて嬉しい限り。
しかも Kotlin で書けるのはとてもよいですね。
正直、C# よりも Kotlin の方が、書いていて気持ちよいですよね。

Kotlin のマルチプラットフォーム対応におけるトピックは２つ。

1. Kotlin/Native による iOS ネイティブアプリのビルドが可能
2. "Common Module" と呼ばれる手法で、プラットフォーム毎の処理を共通化できる

これらについて、 Xamarin と比較しながら見ていきましょうか。

## 1. Kotlin/Native による iOS ネイティブアプリのビルドが可能

これは LLVM 技術を利用して、Kotlin のソースコードから iOS のネイティブバイナリを出力する、というものです。LLVM を使用していることから、iOS 以外にも対応可能とされています。

[Kotlin/Native(iOS)のソースコード](https://github.com/JetBrains/kotlinconf-app/blob/master/ios/src/main/kotlin/ui/AppDelegate.kt) からは、 ``AppDelegate`` や ``UIResponder `` などの CocoaTouch フレームワークの API が Kotlin から使えることが分かります。

さてこの Kotlin/Native、 Xamarin では Xamarin.iOS の AOT(Ahead of Time Compile)で実現していることに近いです。
（Xamarin.iOS の場合、C# のソースコードは中間言語に変換され、デバッグ時はそれが使用される、リリースビルドでは中間言語からネイティブバイナリに”事前”コンパイルされる、という手法なので少し異なります。）

* [Xamarin.iOSの仕組みとアプリケーションの構成 - Build Insider](http://www.buildinsider.net/mobile/insidexamarin/05)
* [iOS Architecture - Xamarin](https://developer.xamarin.com/guides/ios/under_the_hood/architecture/)

また、Swift/Obj-C でない言語から CocoaTouch の API を直接呼ぶ、という意味では、

* RoboVM - Java から CocoaTouch の API が呼べた(後に Xamarin が買収して消滅)
* [RubyMotion](http://www.rubymotion.com/) - Ruby から CocoaTouch の API が呼べる（最近話題にならないね）

などがあり、類似の仕組みを採用していると思われます。

このような「プラットフォーム固有のAPIを、インターフェースを(極力)変えずに他の言語(ここではKotlin)から呼べるようにする」ことを「薄いラッパーを提供する」と私が勝手に言っており、その薄いラッパーがあるが故に、

* プラットフォームのAPI知識がそのまま活かせる
* 同じ言語で開発できるしデバッグできる

というメリットを生みます。

## 2. "Common Module" と呼ばれる手法で、プラットフォーム毎の処理を共通化できる

1 は "iOS の API が Kotlin から呼べる" のみであって、これだけではコード共通化に寄与しません。

コード共通化の "手法" を提供するのがこの Common Module です。
これは、プラットフォーム毎に異なるAPIに対して、共通のAPIを定義し、それにプラットフォーム固有の実装を注入する、というものです。

kotlinconf-app のソースを例に説明してみると、日付を扱う ``Date`` クラスを Common Module を使って作っています。

まず、共通のAPIとしての ``Date`` クラスを common プロジェクト? に定義しています。[実際のコード](https://github.com/JetBrains/kotlinconf-app/blob/master/common/src/main/kotlin/com/jetbrains/kotlinconf/Date.kt) の転載がこちら。

```kotlin
package org.jetbrains.kotlinconf

expect class Date() {
    fun getDate(): Int
    fun getMonth(): Int
    fun getFullYear(): Int
    fun getHours(): Int
    fun getMinutes(): Int
    fun getTime(): Number
}

expect operator fun Date.compareTo(otherDate: Date): Int

expect fun parseDate(dateString: String): Date
expect fun Date.toReadableDateString(): String
expect fun Date.toReadableTimeString(): String

fun Date.toReadableDateTimeString() = "${toReadableDateString()} ${toReadableTimeString()}"
```

``getDate`` や ``parseDate`` など、ありがちなメソッドが定義されていますが、ここに実装はなく、代わりに見慣れないキーワード ``expect`` が付いています。

``expect`` は、「プラットフォーム毎に実装が期待されるモノ」を示すキーワードだと私は解釈しました。

では実装はどこにあるのか？と探してみると、Android (というかJVM)での実装が [``common-jvm/src/main/kotlin/JvmDate.kt``](https://github.com/JetBrains/kotlinconf-app/blob/master/common-jvm/src/main/kotlin/JvmDate.kt) に見つかります。

```kotlin
package org.jetbrains.kotlinconf

import java.text.SimpleDateFormat
import java.util.*
import java.util.Calendar.*

actual class Date {
    private val calendar: Calendar

    actual constructor() {
        calendar = Calendar.getInstance()
    }

    constructor(date: java.util.Date) {
        calendar = Calendar.getInstance().apply {
            time = date
        }
    }

    val date: java.util.Date get() = calendar.time

    actual fun getDate() = calendar[DAY_OF_MONTH]
    actual fun getMonth() = calendar[MONTH]
    actual fun getFullYear() = calendar[YEAR]
    actual fun getHours() = calendar[HOUR_OF_DAY]
    actual fun getMinutes() = calendar[MINUTE]
    actual fun getTime(): Number = calendar.timeInMillis

    override fun equals(other: Any?): Boolean = other is Date && other.calendar.time == calendar.time
}

＜以下省略＞
```

Android では Java SE API が使用できるので、 ``java.util.Date`` や ``java.util.Calendar`` を使用して実装をしています。 expect に対してこちらには ``actual`` というキーワードが付いていますね。

さらに、 [``common-jvm/build.gradle``](https://github.com/JetBrains/kotlinconf-app/blob/master/common-jvm/build.gradle) には、

```java
apply plugin: 'kotlin-platform-jvm'
apply plugin: 'kotlinx-serialization'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
    compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"
    testCompile "junit:junit:4.12"
    testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
    testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
    expectedBy project(":common")
}

＜以下省略＞
```

``expectedBy project(":common")`` という記述があり、これが「common プロジェクトで期待された実際のコードである」ことを定義しています。これがビルドシステムによって解釈され、空っぽの ``common/Date``  クラスが ``common-jvm/Date`` クラスに置き換えられるのでしょう。

同じように iOS 側の Date の実装も…と思ったのですがみつからず（ない？）、さらに他の Date 実装として JavaScript(Kotlin/JS) 側での actual な Date が [``common-js/src/main/kotlin/JsDate.kt``](https://github.com/JetBrains/kotlinconf-app/blob/master/common-js/src/main/kotlin/JsDate.kt) にありました。

```kotlin
package org.jetbrains.kotlinconf

actual external class Date {
    actual constructor()
    constructor(value: Number)

    actual fun getDate(): Int
    actual fun getMonth(): Int
    actual fun getFullYear(): Int
    actual fun getHours(): Int
    actual fun getMinutes(): Int

    actual fun getTime(): Number

    companion object {
        fun parse(string: String): Number
    }
}

＜以下省略＞
```

Kotlin/JS では JavaScript の標準APIである ``Date`` クラスを使って実装をしていますね。


このように共通な ``Date`` クラスを作っておくと、そのクラスは共通なロジック内で使用可能になります。ロジック内で ``isJVM ? java.util.Date() : isJS ? js.Date() : error`` (やべ Kotlin に三項演算子ないんだった)などとプラットフォームごとに分岐しなくても、expect な ``Date`` クラスの actual な実装は、各プラットフォーム向けのビルド時?に置換されます。

では Xamarin ではどうでしょうか？
Xamarin というか .NET の世界では、 **Bait and switch** というテクニックが一般的なものとして知られています。考え方は Common Module と同じで、「APIが定義されているだけの空っぽのモジュール」を "おとり" にして、「プラットフォーム固有の実装がされた実際のモジュール」 を "喰わせる(=置換する)"、というものです。

* [The Bait and Switch PCL Trick](https://log.paulbetts.org/the-bait-and-switch-pcl-trick/)
* [Plugins for Xamarinを作ろう！ - ぴーさんログ](http://ticktack.hatenablog.jp/entry/2016/04/08/180321)
* [共有コードからネイティブ依存処理が使える！PCLを使ったXamarinライブラリ作成テクニック (フェンリル | デベロッパーズブログ)](https://blog.fenrir-inc.com/jp/2015/12/xamarin_plugin.html)

Xamarin 向けはもちろん、複数プラットフォーム対応をうたう .NET 製ライブラリの多くはこのテクニックを使って作られています。ただし Bait and switch はビルドシステムやIDEのサポートがあるわけではない、ただのファイル置換を利用したtrickなので、 ``expect`` や ``actual`` といったキーワードが用意されている Kotlin のそれの方がより洗練されていると言えます。

## 共通化するために、ものすごくたくさんの Common Module を作らなければならない？

はい、その通りだと思われます。

マルチプラットフォームで動作可能な API は現在のところ [Kotlin の標準API](https://kotlinlang.org/api/latest/jvm/stdlib/index.html) だけだと推測されます（それらも全てが使えるかは分からないですし）。
実際、「日付」という基本的なクラスでさえ、 Common Module を自作しなければならない、まだ始まったばかりの状態ですから、今マルチプラットフォーム Kotlin を採用したらほとんどの機能は Common Module を作成しなければならないでしょう。

今後、一般的なクラスやよく使用される機能が、Kotlin標準APIセットに拡充されて（あるいは別のライブラリが作られて）、マルチプラットフォーム対応が進むと思われます。

そんな 一般的なクラス、よく使われる機能などが体系的にまとめられて、マルチプラットフォームに対応しているのが **.NET Standard** です。これは、古くは 非Windows向けの.NET Framework である Mono の登場からそれを基盤とした Xamarin や Unity などの隆盛、Linux でも動作する .NET Core のリリースを経て生まれた **共通API セットの仕様** です。

* [.NET Standard | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/standard/net-standard)
* [特集：マイクロソフトテクノロジーの現在と未来：.NET Standardとは (1/3) - ＠IT](http://www.atmarkit.co.jp/ait/articles/1707/28/news033.html)

Kotlin がこれから「共通APIセットの仕様」を考えていくなら、いっそ .NET Standard に乗っかってくれればいいのになーと Xamarin 使いとしては思うわけですが、

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">kotlin標準ライブラリ拡充しなきゃ→仕様考えるの面倒じゃね？→ここに.NET Standardというものがあります→もうこれに沿えばいいんじゃね？→Kotlin .NET のできあがりっ、COOL!<br>って世界線に入る条件を教えて未来のenoさん <a href="https://t.co/VMXjHwn2pz">https://t.co/VMXjHwn2pz</a></p>&mdash; あめい@ハイドラ待ち (@amay077) <a href="https://twitter.com/amay077/status/926685937819684864?ref_src=twsrc%5Etfw">2017年11月4日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">わたしの予知能力では、Java派「iOSでもRoboVMの実装を使い回せば…」Swift派「Swift for AndroidでFoundationを使い回せば…」C# 派「Xamarinなりe4kなりでSystem.*を使えば…」で標準ライブラリ統一戦争になる未来までしか(ry <a href="https://t.co/qNVyfXwMlo">https://t.co/qNVyfXwMlo</a></p>&mdash; Atsushi Eno (@atsushieno) <a href="https://twitter.com/atsushieno/status/926687187239804929?ref_src=twsrc%5Etfw">2017年11月4日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

まあ、 .NET Standard で統一されたらそれこそ .NET のディストピアなので、この混沌の現状こそが我々の望んでいた世界なのかもですね。

## 開発ツールはどうなるんでしょう？

Android は Android Studio を使うわけですけど、 Kotlin/Native や Kotlin/JS は何をつかうのでしょう？ やっぱり [IntelliJ IDEA](https://www.jetbrains.com/idea/) でしょうか。JetBrains としてはここでマネーを得たい考えなのでしょうかねー。

オープンソースプロダクトの開発者さんは、 「JetBrains のOSS開発者向けライセンス」を得るチャンスがあるので、これを機に取得してみてもよいと思います。

* [JetBrains OpenSource License を更新しました - Experiments Never Fail](https://blog.amay077.net/blog/2017/10/01/renuew_jetbrains_opensource_lisence_2017/)

（ Kotlin/Native の IDE は [CLion](https://www.jetbrains.com/clion/) のようですね。もちろん [現段階の情報](http://kotlinlang.org/docs/reference/native-overview.html) では。）

※JetBrains OpenSource License は、OSS向けのライセンスですので、OSS活動以外での利用はできません、念のため。

<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">「JetBrains のOSS開発者向けライセンス」というか「OSSプロジェクト向けライセンス」なので、OSSプロジェクト以外には使えませんので悪しからず</p>&mdash; 山本 ユースケ (@yusuke) <a href="https://twitter.com/yusuke/status/927366201746976768?ref_src=twsrc%5Etfw">2017年11月6日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


## Welcome to Cross-Platform World

ということで Kotlin のマルチプラットフォーム対応についての感想を Xamarin と比べる形で書いてみました。
Xamarin のそれに比べて Kotlin が秀でているのは、

* Kotlin という C# よりもモダンな言語が使用できる
* Kotlin/JS で Webアプリ もカバーする

という点でしょうか。特に後者は Microsoft としては TypeScript を持っているので C# がここに踏み込むことはなさそうです([WebAssembly はあると思う](https://www.hanselman.com/blog/NETAndWebAssemblyIsThisTheFutureOfTheFrontend.aspx))。 Microsoft 自身も Skype アプリを刷新した際に [ReactXP](https://github.com/Microsoft/reactxp) という React/JS ベースのライブラリを使用している理由として「Xamarin は Webアプリの助けにはならないから」と [言って](https://microsoft.github.io/reactxp/blog/2017/04/06/introducing-reactxp.html) います。 

このようにクロスプラットフォーム開発ツールといっても、それぞれ得意分野や可・不可領域があり、また現在できることを把握し、将来性を予想しつつ、共通化コードを最大化したいという欲求と戦う必要があります。

ここまで読まれた方なら言わずもがなですが、Kotlin のマルチプラットフォーム対応でも「iOSの知識が必要ない」なんてことは口が裂けても言えないわけで、個別のプラットフォームの知識を習得した上で、それらを共通化するための手法・テクニックを学び、実戦する必要があります。

ということで、 @AyaseSH さんのツイートをお借りして締めます、「ようこそクロスプラットフォームへ！」。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Kotlin でクロスプラットフォームになったことで （辛いことが共有できる）仲間が増えたとしか思わないなー。ようこそクロスプラットフォームへ。</p>&mdash; オータガー@ざまりんフレンズ (@AyaseSH) <a href="https://twitter.com/AyaseSH/status/926636856627699717?ref_src=twsrc%5Etfw">2017年11月4日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>