[Xcode/Swift] Swift ⇄ Kotlin 脳内スイッチ・チートシート①

はじめに

1.1 対象読者

  • 対象:Swift または Kotlin のどちらかに慣れていて、もう一方へ“脳内スイッチ”したい iOS/Android/共通化エンジニア
  • ゴール:型・宣言・非同期・エラー・ADT を最短経路で置き換えられるようにする

※ ADT = 抽象データ型 (Abstract Data Type、クラスとか構造体を指す)

1.2 本記事の読み方

  • 2章の対応表で“あの書き方は Kotlin/Swift だと何?”を一気に変換
  • その後、各章のサンプルを流し読み → 実務コードへコピペ&調整

クイック対応表

2.1 基本型マッピング(数値・文字列・真偽・コレクション)

概念SwiftKotlin備考
整数Int(64bit相当), UIntInt(32bit), Long(64bit)ビット幅に注意
実数Double, FloatDouble, Float既定は両者とも Double が多い
真偽BoolBoolean
文字CharacterChar
文字列StringString
配列[T] / Array<T>List<T> / MutableList<T>Swift配列は値型、Kotlinはインターフェース+実装
辞書[K: V] / DictionaryMap<K, V> / MutableMap<K, V>
集合Set<T>Set<T> / MutableSet<T>
バイト列DataByteArray
日付DateInstant/LocalDateTime などJava Time API を利用
任意型Any, AnyObjectAnyKotlin の Any? は null 許容

コレクションの生成例:

// Swift
let list: [Int] = [1, 2, 3]
let dict: [String: Int] = ["a": 1]
var set: Set<String> = ["a", "b"]

// Kotlin
val list: List<Int> = listOf(1, 2, 3)
val dict: Map<String, Int> = mapOf("a" to 1)
val set: MutableSet<String> = mutableSetOf("a", "b")

2.2 値/参照・不変/可変(struct/class vs data class/class, let/var vs val/var

Swift:

  • struct値型class参照型。変数束縛は let(不変)/var(可変)。
  • let user = User(name: "A") なら user は再代入不可。struct のプロパティは userlet だと全体的に不変。

Kotlin:

  • data class/class参照型。フィールドの不変は val(読み取り専用)/var(可変)で表現。
  • data classequals/hashCode/copy を自動生成。

例:

// Swift
struct User {
    var name: String
}

var user1 = User(name: "Alice")
var user2 = user1
user2.name = "Bob" // user1のnameは変わらない

// Kotlin
data class User(
    var name: String
)

val user1 = User("Alice")
val user2 = user1.copy(name = "Bob") // user1は変わらない

2.3 Null安全とオプショナル(T?/if let/guard vs T?/?./?:/!!

Swift:

  • T? が Optional。if let / guard let でアンラップ。?.(チェーン)、??(nil結合)。

Kotlin:

  • T? が Nullable。?.(セーフコール)、?:(Elvis)、!!(非推奨・実務では極力使わない)。

例:

// Swift
func greet(_ name: String?) -> String {
    guard let name else { return "Hi, guest" }
    return "Hi, \(name)"
}
let length = name?.count ?? 0

// Kotlin
fun greet(name: String?): String =
    name?.let { "Hi, $it" } ?: "Hi, guest"

val length = name?.length ?: 0

2.4 列挙と代数的データ型(enum + associated valuessealed class/interface

Swift:

  • 関連値付き enum で ADT を自然に表現。switch は網羅性チェックあり。

Kotlin:

  • sealed class/sealed interfacedata class/object の組合せで ADT。when で網羅性チェック(else 不要にできる)。

例:

// Swift
enum Result<T> {
    case success(T)
    case failure(Error)
}

func handle(_ r: Result<Int>) {
    switch r {
    case .success(let value):
        print(value)
    case .failure(let error):
        print(error.localizedDescription)
    }
}

// Kotlin
sealed interface Result<out T> {
    data class Success<T>(val value: T): Result<T>
    data class Failure(val error: Throwable): Result<Nothing>
}

fun handle(r: Result<Int>) = when (r) {
    is Result.Success -> println(r.value)
    is Result.Failure -> println(r.error.message)
}

2.5 エラーハンドリング(throws/trytry/catch/Result

Swift

  • 投げる関数は throws、呼び出しは trytry?Optional に変換、try! はクラッシュ要因。do-catch で捕捉。

Kotlin

  • チェック例外は存在せず、try/catch で捕捉。関数型合成や戻り値で扱いたい時は Result<T> / runCatching を使う。

例:

// Swift
enum MyError: Error {
    case oops
}

func risky() throws -> Int {
    throw MyError.oops
}

let a: Int? = try? risky() // nil

do {
    _ = try risky()
} catch {
    print(error)
}

// Kotlin
class MyError: RuntimeException("oops")
fun risky(): Int { throw MyError() }

val a: Result<Int> = runCatching { risky() }
a.getOrElse { 0 } // 0

try {
    risky()
} catch (e: MyError) {
    println(e)
}

2.6 非同期処理(async/await/Tasksuspend/Coroutines/Flow

Swift

  • 構造化並行性(async/awaitTaskasync letTaskGroupActor)。キャンセルは協調的。

Kotlin

  • suspend 関数+Coroutines(CoroutineScope/launch/async/withContext)と Flow。同じく協調的キャンセル。

例:

// Swift
func fetchA() async throws -> Int { 1 }
func fetchB() async throws -> Int { 2 }

func fetchBoth() async throws -> Int {
    async let a = fetchA()
    async let b = fetchB()
    return try await a + b // 3
}

// Kotlin
suspend fun fetchA(): Int = 1
suspend fun fetchB(): Int = 2

suspend fun fetchBoth(): Int = coroutineScope {
    val a = async { fetchA() }
    val b = async { fetchB() }
    a.await() + b.await()
}

2.7 拡張と合成(Swift の extension/プロトコル指向 ↔ Kotlin の拡張関数/インターフェース)

Swift

  • extension で後付けメソッド/プロトコル適合。プロトコル+デフォルト実装で合成。

Kotlin

  • 拡張関数/プロパティで後付け API。interface にデフォルト実装も可。両者とも静的ディスパッチ(実行時オーバーライドではない)に注意。

例:

// Swift
protocol Printable {
    func text() -> String
}

extension Int: Printable {
    func text() -> String { "\(self)" } // selfは、Int型
}

// Printableに準拠している要素を持つCollectionに対する拡張
extension Collection where Element: Printable {
    func joinedText() -> String { map { $0.text() }.joined(separator: ",") }
}

// Kotlin
interface Printable { 
    fun text(): String
}

fun Int.text(): String = "$this"

fun <T: Printable> Collection<T>.joinedText(): String =
    this.joinToString(",") {
        it.text() 
    }