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の置き換え
foo ?? "-"
foo ?: "-"
宣言時に値を入れられないがOptionalとして扱いたくない場合
var foo: String!
lateinit var foo: String
オプショナルチェイン
Swiftと違ってKotlinは?を毎回書く必要がある
a?.b.c.d.e
a?.b?.c?.d?.e
コレクション
List、Map、Setがある。
値の追加などの変更操作をする場合は、MutableListなどMutableをつけたクラスを使う必要がある。
class Foo {
// 下記2行はどちらも同じ意味
val list1: MutableList<String> = mutableListOf()
val list2 = mutableListOf<String>()
}
List
let list = ["1", "2", "3"]
for str in list {
print(str)
}
list.forEach {
print($0)
}
// インデックスを使う場合
list.enumerated().forEach {
print("\($0.offset): \($0.element)")
}
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からしか使えない。
tasks.forEach { key, value -> }
APILevel24未満の場合は entry 指定のものを使う
tasks.forEach { entry -> }
static 変数、関数はない
Companion Objectを使って同じようなことが実現できる
class Hoge {
companion object {
fun foo() {}
}
}
Hoge.foo()
シングルトンの作り方
classの代わりにobjectキーワードを使うとシングルトンになる。シングルトンにはクラス名(オブジェクト名?)+ドットでアクセスできる
object Hoge {
fun method() {}
}
Hoge.method()
if let の書き方
if 文でnullチェックすれば、ifブロックの中では not-null として扱える。
(チェックした内容に応じて自動castされる、smart cast という機能)
fun boo(foo: Foo?) {
if (foo != null) {
foo.bar() // not-null なので ? をつけず使える
}
}
ただし、propertyなど、nullチェックするスコープの外で値が変わり得る場合は、スコープ内で変数を再定義してnullチェックしないと smart castされない。
class Sample {
var foo: String? = null
fun boo() {
val foo = foo // nullチェック前にローカルスコープで再定義しないとsmart castされない
if (foo != null) {
foo.bar()
}
}
}
let を使って以下のようにも書けるが、多くの場合は上記の smart cast を使うことを推奨する。
val name: String? = "John Doe"
name?.let {
print("${it}")
}
※it はSwiftの $0 のような役割
また、letは?を書き忘れると null でも実行されてしまい、特にコンパイルエラーも出ないという罠があり危険。
引数のラベルはつけなくても良い
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など)の += の使い方
val list = mutableListOf<String>()
list += "a"
MutableCollection は += で値を追加できるが、その場合 val で宣言する必要がある。
var で宣言すると、+ 部分が別のオペレーターとも判断できるのでコンパイルエラーになる。
willSet、didSet
var foo: Int = 0 {
willSet {
print("willSet \(foo) to \(newValue)")
}
didSet {
print("didSet \(foo)")
}
}
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はドットが一つ少ない
let range = 1...10
val range = 1..10
1以上10未満(10は含まない)の例
Kotlinはドットではなく until
を使う。
let range = 1..<10
val range = 1 until 10
その他
閉じないRangeに該当する書き方はKotlinにはなさそう。
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ではできなさそう。
何か代わりがあればいいが。
extension UIViewController: UITableViewDelegate {
}
演算子のオーバーロード
まとめると演算子には英名がついてるので、その名前でfunctionを定義してやればよい。
演算子を定義する場合、funの前にoperator修飾子をつける必要がある。
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
改行、バックスラッシュ、ダブルコーテーションなどをエスケープしなくて良い。
val text = """
for (c in "foo")
print(c)
"""
Genericsについて
長いのでこちらに切り出した。
Reflectionについて
長くなりそうなのでこちらに切り出した。
enumの継承
enum classは継承ができないが、sealed class は継承ができる。
sealed class HttpTaskError: Exception() {
object NETWORK: HttpTaskError()
object STATUS_CODE: HttpTaskError()
object PARSE: HttpTaskError()
object INVALID_RESPONSE: HttpTaskError()
}
enumに値を持たせる
enum class Status(val code: String) {
success("0"),
fail("1")
}
複数のenum classで同じプロパティやファンクションをもたせたい場合、上記のsealed classでも実現できるが、interfaceの方が手っ取り早い。
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::ファンクション名 の形でメンバーファンクションへの参照を取得できる。
class Foo {
val piyo: ()->Unit = this::hoge
fun hoge() {}
}
メソッド1つの無名クラスの省略記法(SAM変換)
メソッドが1つだけの interface は無名クラスを省略形で書ける。
SAM(single abstract method)変換という。
// 省略前
button.setOnClickListener(object: View.OnClickListener {
override fun onClick(view: View?) {
// クリック処理
}
})
// 省略後
button.setOnClickListener {
// クリック処理
}
ただし、この機能はJavaで定義されたinterfaceでしか使えないという中途半端な実装状態。
Kotlinのinterfaceも対応されることが望まれる。
複数型の宣言
extension UITableView {
// 引数のdelegate は UITableViewDelegate かつ UITableViewDataSource
func setup(delegate: UITableViewDelegate & UITableViewDataSource) {
self.delegate = delegate
self.dataSource = delegate
}
}
Kotlinの場合どう書く?
guard let
guard let bar = bar else {
return
}
val bar = bar ?: return
クラスオブジェクトの取得
Swiftはクラス名.self
でクラスオブジェクトを取得できるが、Kotlinはクラス名::class
でクラスオブジェクト(KClass)を取得する。
class Foo {}
func handleClass(class: AnyClass) {}
func run() {
handleClass(class: Foo.self)
}
class Foo {}
fun handleClass(klass: KClass<*>) {}
fun run() {
handleClass(klass = Foo::class)
}