59
61

More than 5 years have passed since last update.

Swift to Kotlinチートシート

Last updated at Posted at 2018-01-26

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を作るのがベスト。設計に関する詳細は以下に記載。
iOSのソースをなるべくそのままAndroidに移植する方法

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

コレクション

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

クラスオブジェクトの取得

Swiftはクラス名.selfでクラスオブジェクトを取得できるが、Kotlinはクラス名::classでクラスオブジェクト(KClass)を取得する。

Swift
class Foo {}

func handleClass(class: AnyClass) {}

func run() {
    handleClass(class: Foo.self)
}    
Kotlin
class Foo {}

fun handleClass(klass: KClass<*>) {}

fun run() {
    handleClass(klass = Foo::class)
}   
59
61
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
59
61