はじめに
SwiftとKotlinを使い始めて6年くらい経ちました。
よく似ているSwiftとKotlinですが、両方を使い続けるうちに、私の中では総合点はKotlinの方が上という印象が固まってきたので、優劣のあるポイントを紹介したいと思います。
KotlinがSwiftより優れている10の点
1. Swiftは循環参照によるメモリリークが多発する
Swiftの一番嫌な点は、循環参照するとメモリリークすることです。
特にクロージャー内でself
を参照してメモリリークが起きることがよくあります。
対応としては、weak
やunowned
を書いて弱参照にするだけなんですが、初級者はほぼ抜けますし、理解していても、たまに書き忘れます。
XcodeやLintで機械的にチェックできればいいのですが、そういった方法も未だに確立されていません。
(頑張れば一応近いことはできるけど)
メモリリークの調査には結構時間がかかります。[weak self]
を書き忘れただけで、そのようなメモリリークが起きてしまうのは大きなデメリットと言えます。
一方、Kotlinはガベージコレクションがいい感じにやってくれるので、循環参照しても何も問題はありません。
2.Swiftはクロージャー内でselfを書く必要がある
Swiftでは @escaping
指定されたクロージャー内でインスタンスメンバーにアクセスするときself.
を書く必要があります。
class Foo {
var number = 0
func example() {
capture {
// self.を書く必要がある
print(self.number)
}
}
}
func capture(function: @escaping () -> Void) {}
頻繁にselfを書くことになるので、これが地味に手間です。
また、クロージャー内のコードを関数に切り出すと、今度は逆にself.
が必要なくなるので、削除する必要があります。
(self.がついたままでも動くけど、いらないのでできれば消したい)
この仕様は、クロージャー内でselfを参照するとメモリリークを起こすことをプログラマーに意識させるためのものだと思いますが、もうちょっといい感じにして欲しいと思います。
追記
2023-03 Swift5.8で改善されました 🎉
guard let self
をすると以降 self
を省略できます。
var number = 0
func example() {
capture { [weak self] in
guard let self else { return }
print(number)
}
}
3.Kotlinの方が配列・マップの操作関数が豊富
Swiftにおける配列やディクショナリ、Kotlinにおけるリストやマップにおいて、Kotlinの方が操作関数が豊富で使いやすい印象があります。
例えば、Kotlinにはリストをマップに変換する機能がありますが、これはSwiftにはありません。
val list = listOf(
Pair("A組", "田中"),
Pair("A組", "鈴木"),
Pair("B組", "伊藤"),
Pair("B組", "佐藤"),
Pair("C組", "高橋"),
)
val map = list.groupBy { it.first }
このコードで以下のように組みごとにグルーピングされたマップを作ることができます。
A組=[(A組, 田中), (A組, 鈴木)],
B組=[(B組, 伊藤), (B組, 佐藤)],
C組=[(C組, 高橋)]
追記
Dictionary(grouping:by:)
で似たことができますが、KotlinのgroupByがArrayの順序を保持するのに対し、こちらは順序を保持しないため、やや使い勝手が悪いです。
4.Kotlinは if や when が値を返せる
Kotlinではif文やwhen文が値を返すことができます。
ブロックの最後が自動で戻り値になるという仕様と組み合わせて、以下のようにしてif文の戻り値を直接変数に代入できます。
val result = if (condition) {
"true"
} else {
"false"
}
Swiftにはこの機能はないので、以下のようにやや冗長な書き方をせざるを得ません。
let result: String
if condition {
result = "true"
} else {
result = "false"
}
追記
Swift 5.9で、ifやswitchが値を返せるようになるようです。
@zunda_pixel さん情報ありがとうございます。
5.Kotlinにはスコープ関数がある
Kotlinには以下のスコープ関数と呼ばれる、どのオブジェクトでも使える便利関数があるのですが、Swiftには同じようなものがありません。
- let
- with
- run
- apply
- also
ここでは詳細は説明しませんが、これらの関数はたまに便利です。
6.Swiftは文字列の切り出しがめんどい
Swiftは文字列から範囲を指定して部分文字列を切り出すのが面倒です。
以下は文字列 text
の1〜3文字目を切り出すコードですが、Kotlinと比べるといかにSwiftが面倒かわかります。
let from = text.index(text.startIndex, offsetBy: 1)
let to = text.index(text.startIndex, offsetBy: 3)
let sub = text[from...to]
let result = String(sub)
val result = text.substring(1..3)
7.Kotlinは数値の計算で暗黙の型変換をしてくれる
Kotlinでは異なる型の数値でも、明示的な型変換なしで四則演算することができます。
例えば、IntとDoubleの値をそのまま + で合計することができます。
val a: Int = 1
val b: Double = 1.0
val result = a + b // そのまま足せる
一方Swiftにこの機能はなく、異なる型同士で計算する場合は、明示的に型を変換してやる必要があります。
let a: Int = 1
let b: Double = 1
let result = Double(a) + b // aをDoubleに変換する必要がある
Swiftの方が曖昧さがなくていいという意見もありそうですが、慣れてくるとやっぱり型変換を書かなくていいKotlinの方が便利です。
8.Swiftはオブジェクトの初期化中にインスタンスメンバーを使えない
Swiftのクラスや構造体は、オブジェクトの初期化中にインスタンス変数、インスタンス関数を使うことができません。
class Foo {
var a = 1
// ❌ 初期化中にインスタンス変数aにアクセスしているのでエラーになる
var b = a
// ❌ 初期化中にインスタンス関数dにアクセスしているのでエラーになる
var c = d()
func d() -> Int { 0 }
}
lazy
を使えば似たようなことはできますが、初期化中でもインスタンスメンバーを使えるKotlinと比べると面倒です。
9.Swiftはジェネリクスに制限が多い
Swiftはジェネリクスを用いたクラスやプロトコルにKotlinより制限が多く、ジェネリックスを使うとコンパイルエラーと格闘するハメになることが多いです。
例えば、Swiftにおけるジェネリクスには以下の制限があります。
- ジェネリッククラスはstaticプロパティを定義できない
- 任意の型パラメーターのジェネリッククラスの変数が作れない
- プロトコルの場合、ジェネリッククラスとは異なるAssociated Typeの形でジェネリクスを定義する必要がある
- Associated Typeもこれまた制限が多い
// ❌ プロトコルをジェネリクスにはできない
protocol Foo<T> {}
class Foo<T> {
// ❌ ジェネリッククラスでstatic変数は定義できない
static var a = 1
}
struct Store {
// ❌ Tが任意のFoo型変数は作れない
let foo: Foo<*>
}
Kotlinでは上記の制限はどれもありません。
10.Swiftはリフレクションが弱い
まあ、アプリの実装で使うことはほとんどないかもしれませんが、Kotlinと比べるとSwiftはリフレクションの機能が少なく、がっつりリフレクションを使った仕組みを作るのは難しそうです。
番外編:KotlinよりSwiftの方が優れているところ
Kotlinの優位な点ばかり挙げましたが、当然Swiftの方が優れている点もあるので、少し挙げておきます。
Swiftは構造体を値渡しできる
Swiftのstruct
は引数や戻り値にした場合、コピーされ値渡しになりますが、Kotlinには同じ機能がありません。
Swiftのコードを全く同じようにKotlinに移植しても、この違いによってKotlin側だけバグっていたことが何度かありました。
Swiftは型名.
が型推論で省略できる
Swiftは型推論できる場合、型名.
の型名の部分を省略することができます。
例えば、Color
というenumを引数にとる、setColorという関数があった場合、Swiftでは Color.red
のColorの部分を省略して、.red
のように書くことができます。
setColor(.red)
一方Kotlinはこの省略はできず、以下のようにColorから書く必要があります。
setColor(Color.red)
複数のenumを並べる場合などで、この省略ができるとだいぶコードをスッキリさせることができますし、enum以外でもたまに役に立つ時があります。
Swiftはifやwhileの条件式を括弧で囲まなくていい
Kotlinはifやwhileの条件式を括弧で囲む必要がありますが、Swiftは括弧を書く必要がありません。
if condition {
// コード
}
if (condition) {
// コード
}
些細な違いではありますが、書かなくていいなら書かない方が楽です。
おわりに
他にも違いは色々あると思いますが、特に気になる違いを挙げてみました。
全体的に、Kotlinの方が配慮があって人に優しい印象ですね。