Kotlin
Swift

Swift to Kotlinチートシート

SwiftをKotlinに書き換えるにあたってのチートシート。

コード変換

angelolloqui/SwiftKotlin
このツールでSwiftからKotlinへの静的なコード変換ができる。

こちらのページから直近リリースの変換ツール(SwiftKotlinApp.2017-12-10.20-14-06.zipなど)をダウンロードできる

便利なツールだが完璧な変換ができるわけではなく、ざっと確認したところ以下の問題があった。

  • class var、class funcが上手く変換されない(staticは大丈夫)
  • enum内のfuncなどが変換されない
  • タプルが上手く変換されない(Kotlinにはそもそもタプルがない)
  • deferブロックが上手く変換されない
  • case文にenumをフル修飾で書く( case Animal.dog: のような形 )とエラーになった

このツールでベースのKotlinコードを作って、細部を手直ししていく。

UIViewControllerのviewをAndroidの何に置き換えるか

  • Activity
  • Fragment
  • ConstraintLayout、FrameLayoutなど

上記のような候補が考えられたが、今のところFragmentが一番妥当という結論。
(ノウハウはあまりないので後で覆る可能性はあり)

2018/5/20追記: 覆った。Fragmentは使わずシンプルなViewGroupを持つUIViewControllerを作るのがベスト。

Activityが複数無ければ各ActivityのlaunchModeなどを考える必要がなくなり、データ連携もiOSと似せることができる。
ただし、onConfigurationChangedなどは一括でハンドルして、画面毎に処理を分けてやる必要があったりパフォーマンスの面で検証が必要。

optional

使い方はSwiftと似てるが違いもある。

Nullの置き換え

Swift
foo ?? "-"
Kotlin
foo ?: "-"

宣言時に値を入れられないがOptionalとして扱いたくない場合

Swift
var foo: String!
Kotlin
lateinit var foo: String

オプショナルチェイン

Swiftと違ってKotlinは?を毎回書く必要がある

Swift
a?.b.c.d.e
Kotlin
a?.b?.c?.d?.e

コレクション

https://qiita.com/opengl-8080/items/36351dca891b6d9c9687

List、Map、Setがある。
値の追加などの変更操作をする場合は、MutableListなどMutableをつけたクラスを使う必要がある。

Kotlin
class Foo {
   // 下記2行はどちらも同じ意味
   val list1: MutableList<String> = mutableListOf()
   val list2 = mutableListOf<String>()
}

List

Swift
let list = ["1", "2", "3"]

for str in list {
    print(str)
}

list.forEach {
    print($0)
}

// インデックスを使う場合
list.enumerated().forEach {
    print("\($0.offset): \($0.element)")
}
Kotlin
val list = listOf("1", "2", "3")

for (str in list) {
    Log.d("TEST", str)
}

list.forEach {
    Log.d("TEST", it)
}

// インデックスを使う場合
list.forEachIndexed { index, str ->
    Log.d("TEST", "${index}: ${str}")
}

Map

KeyValue指定のforEachはAPILevel24からしか使えない。

Kotlin
tasks.forEach { key, value ->  }

APILevel24未満の場合は entry 指定のものを使う

tasks.forEach { entry ->  }

static 変数、関数はない

Companion Objectを使って同じようなことが実現できる

Kotlin
class Hoge {
    companion object {
        fun foo() {}
    }
}
Hoge.foo()

シングルトンの作り方

classの代わりにobjectキーワードを使うとシングルトンになる。シングルトンにはクラス名(オブジェクト名?)+ドットでアクセスできる

Kotlin
object Hoge {
    fun method() {}
}
Hoge.method()

if let の書き方

if 文でnullチェックすれば、ifブロックの中では not-null として扱える。
(チェックした内容に応じて自動castされる、smart cast という機能)

Kotlin
fun boo(foo: Foo?) {
    if (foo != null) {
        foo.bar() // not-null なので ? をつけず使える
    }
}

ただし、propertyなど、nullチェックするスコープの外で値が変わり得る場合は、スコープ内で変数を再定義してnullチェックしないと smart castされない。

Kotlin
class Sample {
   var foo: String? = null
   fun boo() {
      val foo = foo // nullチェック前にローカルスコープで再定義しないとsmart castされない
      if (foo != null) {
         foo.bar()
      }
   }
}

let を使って以下のようにも書けるが、多くの場合は上記の smart cast を使うことを推奨する。

Kotlin
val name: String? = "John Doe"
name?.let {
   print("${it}")
}

※it はSwiftの $0 のような役割

また、letは?を書き忘れると null でも実行されてしまい、特にコンパイルエラーも出ないという罠があり危険。

引数のラベルはつけなくても良い

Kotlin
fun foo(arg: String) {}
foo(arg = "Hello")
foo("Hello")

引数ラベル(上記の例ではarg)は書いても書かなくても良い。

クラスとコンストラクターの書き方(継承など)

長いのでこちらにまとめた。

val は Swiftの let とは意味が異なる

val は immutable ではなく readonly を表す。
Swiftの場合 let 宣言した Array は値の追加などをできないが、val 宣言した List は値の追加などができる。
Listに対して += で値を加えたい場合、val で宣言しないと演算子の解釈が2通り発生してコンパイルエラーになる。

MutableCollection(MutableListなど)の += の使い方

Kotlin
val list = mutableListOf<String>()
list += "a"

MutableCollection は += で値を追加できるが、その場合 val で宣言する必要がある。
var で宣言すると、+ 部分が別のオペレーターとも判断できるのでコンパイルエラーになる。

willSet、didSet

Swift
var foo: Int = 0 {
    willSet {
        print("willSet \(foo) to \(newValue)")
    }
    didSet {
        print("didSet \(foo)")
    }
}
Kotlin
var foo: Int = 0
    set(value) {
        Log.d("tag", "willSet ${field} to ${value}");
        field = value
        Log.d("tag", "didSet ${value}");
    }

Range

1以上10以下(10も含む)の例

Kotlinはドットが一つ少ない

Swift
let range = 1...10 
Kotlin
val range = 1..10 

1以上10未満(10は含まない)の例

Kotlinはドットではなく until を使う。

Swift
let range = 1..<10 
Kotlin
val range = 1 until 10 

その他

閉じないRangeに該当する書き方はKotlinにはなさそう。

Swift
let range = 1...

DataBinding

Android Studio3.0.1での設定。
DataBindingを使うにはbuild.gradleに以下の設定が必要。

apply plugin: 'kotlin-kapt'
android {
    dataBinding {
        enabled = true
    }
}
dependencies {
    kapt 'com.android.databinding:compiler:3.0.0-alpha5'
}

ただしこの設定だけではDataBindingのクラスが自動更新されない(Rebuild Projectしないと更新されない)問題があり、現在調査中。
また、このビルド設定でObservablePropertyで変数監視するとエラーになる。
追記:AndroidStudio3.1ではdependenciesのkaptの記載が不要になるとの情報あり

以下も必要との記載をいくつか見かけたが、なくても動いてる。
(むしろ最近のAndroidStudioでは記載するとエラーになる?)

kapt {
    generateStubs = true
}

ラムダ式の return は親のreturnになってしまう場合がある

Iterable.map などに指定するラムダ式は、値を帰す場合もreturnは記載せず、以下のような形で書く。

listOf<String>().map { it }

mapはinline functionのため、以下のようにreturnを書くと呼び出し元のfunctionにコード展開され、呼び出し元がreturnされてしまう。

listOf<String>().map { return it }

return を書きたい場合は、以下の @map のようにラベルを付けて書くことができる。

listOf<String>().map { return@map it }

16進数表記はLong

0x表記で16進数で数値を書けるが、型はIntじゃなくLongになるので適宜変換が必要。

ColorDrawable(0xFFFF6666.toInt())

Extensionにインターフェースはかませない

Swiftの以下のような書き方はKotlinではできなさそう。
何か代わりがあればいいが。

Swift
extension UIViewController: UITableViewDelegate {
}

演算子のオーバーロード

まとめると演算子には英名がついてるので、その名前でfunctionを定義してやればよい。
演算子を定義する場合、funの前にoperator修飾子をつける必要がある。

Kotlin
class Foo(num: Int) {
    val num = num
    operator fun plus(other: Foo): Int {
        return Foo(num + other.num)
    }
}
val result = Foo(1) + Foo(2)

詳しくはこちら
https://qiita.com/KokiEnomoto/items/2fedf864ff0710927b98

raw string

改行、バックスラッシュ、ダブルコーテーションなどをエスケープしなくて良い。

Kotlin
val text = """
    for (c in "foo")
        print(c)
"""

Genericsについて

長いのでこちらに切り出した。

Reflectionについて

長くなりそうなのでこちらに切り出した。

enumの継承

enum classは継承ができないが、sealed class は継承ができる。

Kotlin
sealed class HttpTaskError: Exception() {
    object NETWORK: HttpTaskError()
    object STATUS_CODE: HttpTaskError()
    object PARSE: HttpTaskError()
    object INVALID_RESPONSE: HttpTaskError()
}

enumに値を持たせる

Kotlin
enum class Status(val code: String) {
   success("0"),
   fail("1")
}

複数のenum classで同じプロパティやファンクションをもたせたい場合、上記のsealed classでも実現できるが、interfaceの方が手っ取り早い。

Kotlin
interface TypeCode {
    val code: String
}

enum class Status(override val code: String): TypeCode {
   success("0"),
   fail("1")
}

enum class Category(override val code: String): TypeCode {
   foo("0"),
   bar("1")
}

// クラスとコード値を指定して enum を取得する
inline fun <reified T> enumValue(type: KClass<T>, code: String?) : T? where T : Enum<T>,
 T: TypeCode {
    return enumValues<T>().firstOrNull { it.code == code }
}

メンバーファンクションへの参照

this::ファンクション名 の形でメンバーファンクションへの参照を取得できる。

Kotlin
class Foo {
    val piyo: ()->Unit = this::hoge
    fun hoge() {}
}

メソッド1つの無名クラスの省略記法(SAM変換)

メソッドが1つだけの interface は無名クラスを省略形で書ける。
SAM(single abstract method)変換という。

Kotlin
// 省略前
button.setOnClickListener(object: View.OnClickListener {
   override fun onClick(view: View?) {
      // クリック処理
   }
})
// 省略後
button.setOnClickListener { 
   // クリック処理
}

ただし、この機能はJavaで定義されたinterfaceでしか使えないという中途半端な実装状態。
Kotlinのinterfaceも対応されることが望まれる。

複数型の宣言

Swift
extension UITableView {

    // 引数のdelegate は UITableViewDelegate かつ UITableViewDataSource
    func setup(delegate: UITableViewDelegate & UITableViewDataSource) {
        self.delegate = delegate
        self.dataSource = delegate
    }
}

Kotlinの場合どう書く?

guard let

Swift
guard let bar = bar else {
   return
}
Kotlin
val bar = bar ?: return