久々にiOSアプリのコードを書く機会があったのですが、「KotlinのあれってSwiftではどう書くんだっけ?」となる場面が結構多かったので、順次まとめていこうと思います。
なお、シンプルな置換でおおむね完結するものについては、以下の表などをご参照ください。
Smart Cast
Kotlinでは、nullableとして宣言された変数であっても、non-nullであると保証されるスコープ内ではnon-nullな変数にキャストされるSmart Castという機能があります。
Swiftではif let
を使用することで同様のことが実現できます。
- Kotlin
val name: String? = "hoge" print(name) // hoge if (name != null) { // Smart Castにより、nameはこのスコープ内ではnon-nullな変数として扱えます。 print("hello, ${name.toUpperCase()}") // hello, HOGE } // nameはここではnullableな値として扱われます。 print(name?.toUpperCase()) // HOGE
- Swift
let name: String? = "hoge" print(name) // Optional(hoge) if let nonOptionalName = name { // nameがnon-optionalな値のときにこのスコープ内の処理が実行されます。 // Optional Bindingにより、nonOptionalNameはこのスコープ内でnon-optionalな変数として扱えます。 print("hello, \(nonOptionalName.uppercased())") // hello, HOGE } // nameはここではoptionalな値として扱われます。 print(name?.uppercased()) // Optional(HOGE)
ただし、上記のKotlinのサンプルコードでは問題ありませんが、スコープ内の処理中にif
でチェックした変数の値が他の箇所から変更される可能性がある場合、その変数に対してSmart Castは行われません。
その場合、スコープ関数のlet
を使用することでSwiftと同様のことが実現できます。
- Kotlin
val name: String? = "hoge" print(name) // hoge name?.let { nonNullName -> // nameがnon-nullな値のときにこのスコープ内の処理が実行されます。 // nonNullNameはこのスコープ内でnon-nullな変数です。 print("hello, ${nonNullName.toUpperCase()}") // hello, HOGE } // nameはここではnullableな値として扱われます。 print(name?.toUpperCase()) // HOGE
nullチェックによる早期return
Kotlinではif
を使用することで早期returnを行なうことができます。
Swiftではguard let
を使用することで同様のことが実現できます。
- Kotlin
val name: String? = "hoge" if (name == null) return // Smart Castにより、nameはここ以降ではnon-nullな変数として扱えます。 ...
- Swift
let name: String? = "hoge" guard let nonOptionalName = name else { return } // Optional Bindingにより、nonOptionalNameはここ以降ではnon-optionalな変数として扱えます。 ...
他の方法として、Kotlinではelvis演算子(?:
)の後にreturnを記述できます。
一方、Swiftではnil-coalescing演算子(??
)の後にreturnを記述できません。
- Kotlin
val name: String? = "hoge" name ?: return // Smart Castにより、nameはここ以降ではnon-nullな変数として扱えます。 ...
- Swift
let name: String? = "hoge" // 🙅このような書き方はできません。 name ?? return
when式
Kotlinのwhen
は文ではなく式なので、最後に評価された値を変数に代入できます。
Swiftのswitch
は文ですが、クロージャ式内で条件判定を行う処理を記述し、即時実行することで擬似的に同様のことが実現できます。
- Kotlin
val score = 70 val grade = when (score) { in 90..100 -> "SA" in 80..89 -> "A" in 70..79 -> "B" in 60..69 -> "C" in 0..59 -> "D" else -> throw IllegalStateException() } print("grade: $grade") // grade: B
- Swift
let score = 70 let grade = { () -> String in let result: String switch score { case 90...100: result = "SA" case 80...89: result = "A" case 70...79: result = "B" case 60...69: result = "C" case 0...59: result = "D" default: fatalError() } return result }() print("grade: \(grade)") // grade: B
Swiftのクロージャ式はKotlinのラムダ式のようなものです。
私自身も違いが明確に説明できないので、詳しくは公式ドキュメントなどをご参照ください。
https://kotlinlang.org/docs/reference/lambdas.html#lambda-expressions-and-anonymous-functionskotlinlang.org
Kotlinのラムダ式内で条件判定を行う処理を記述し、即時実行することでSwiftと同様のことが実現できるにはできます(ただ、まずやらないと思います😇)。
- Kotlin
val score = 70 val grade = { val result: String when (score) { in 90..100 -> { result = "SA" } in 80..89 -> { result = "A" } in 70..79 -> { result = "B" } in 60..69 -> { result = "C" } in 0..59 -> { result = "D" } else -> throw IllegalStateException() } // Kotlinではラムダ式内でreturnが記述できず、最後に評価された値が戻り値となります。 result }() print("grade: $grade") // grade: B
値に対応するenumの取得
例えば、APIからroleId
を受け取り、アプリケーション側ではその値に対応するenum(UserType
)を使用して処理を行いたいケースがあるとします。
Kotlinでは以下のような記述で上記のケースを実現できます。
- Kotlin
// enumの定義 // 規定の値以外のroleIdをプロパティに持つenumオブジェクトを生成しないよう、 // コンストラクタの可視範囲はprivateにしています。 enum class UserType private constructor(val roleId: Int) { ADMIN(0), PREMIUM(1), NORMAL(2), ; companion object { fun getUserTypeByRoleId(roleId: Int): UserType? { return values().find { it.roleId == roleId } } } } // roleIdから対応するenumを取得 val userType = UserType.getUserTypeByRoleId(1) print(userType) // PREMIUM print(userType?.roleId) // 1
一方、SwiftではRaw Valueというものを使用することにより実現できます。
Swiftではenumの型名の後に割り当てたい型を記述することで、その型の値を各列挙子に割り当てることができます。各列挙子に割り当てられている値はrawValue
プロパティとして取得できます。
また、rawValue
を引数に取り、対応するenumオブジェクト(ない場合はnil)を生成するイニシャライザが自動的に追加されます。
- Swift
// enumの定義 // 型名の後にIntを記述することで、各列挙子にInt型の値を割り当てることができます。 enum UserType: Int { case admin = 0 case premium = 1 case normal = 2 } // roleIdから対応するenumを取得 let userType = UserType(rawValue: 1) print(userType) // Optional(UserType.premium) print(userType?.rawValue) // Optional(1)
ただし、上記の用途で割り当てることができるのは整数リテラル(Intなど)、浮動小数点数値リテラル(Doubleなど)、文字列リテラル(Stringなど)だけとなります。
上記以外の型の値では、RawRepresentable
プロトコルに準拠することで同様のことが実現できます。
なお、enumの型名の後に割り当てたい型を記述したときは、そのenumがRawRepresentable
に準拠するようSwiftコンパイラが自動的に処理を追加してくれているようです。
// enumの定義 enum UserType: CaseIterable { case admin case premium case normal } extension UserType: RawRepresentable { typealias RawValue = Int init?(rawValue: RawValue) { // CaseIterableプロトコルに準拠することでallCasesが使用できるようになります。 let enumeratorOrNil = Self.allCases.first { $0.rawValue == rawValue } guard let enumerator = enumeratorOrNil else { return nil } self = enumerator } var rawValue: RawValue { switch self { case .admin: return 0 case .premium: return 1 case .normal: return 2 } } } // roleIdから対応するenumを取得 let userType = UserType(rawValue: 1) print(userType) // Optional(UserType.premium) print(userType?.rawValue) // Optional(1)
Sealed Class
KotlinのSealed Classに相当するものとして、SwiftではAssociated Valueというものがあります。
どちらもenumを拡張したような概念ですが、Kotlinはsealed
修飾子をクラスに対して付与して実現するのに対し、Swiftはenumをそのまま使用して実現します。
- Kotlin
// Sealed Classの定義 sealed class Color { object Red : Color() object Green : Color() object Blue : Color() data class RgbColor(val red: Int, val green: Int, val blue: Int): Color() } val color = Color.RgbColor(255, 255, 255) when (color) { is Color.Red -> { print("red") } is Color.Green -> { print("green") } is Color.Blue -> { print("blue") } is Color.RgbColor -> { // Smart Castにより、colorはこのスコープ内ではRgbColor型の変数として扱えます。 print("red: ${color.red}, green: ${color.green}, blue: ${color.blue}") } }
- Swift
// Associated Value(enum)の定義 enum Color { case red case green case blue case rgbColor(red: Int, green: Int, blue: Int) } let color = Color.rgbColor(red: 255, green: 255, blue: 255) switch color { case .red: print("red") case .green: print("green") case .blue: print("red") case let .rgbColor(r, g, b): // case letと記述することによって、switchに指定したcolorの各プロパティが // それぞれr、g、bに渡されます。 print("red: \(r), green: \(g), blue \(b)") }
遅延初期化
Kotlinではby lazy
によって遅延初期化ができます。
Swiftではlazy var
によってほぼ同様のことが実現できます。ただし、Swiftではvar
のため値の再代入が可能となります。
- Kotlin
val name: String by lazy { print("initialized") "hoge" } val name1 = name // 初回アクセス時だけinitializedがprintされる val name2 = name // 2回目以降は初回の評価結果のキャッシュが返却される // 🙅valなので再代入できない。 name = "fuga"
- Swift
lazy var name: String = { print("initialized") return "hoge" }() let name1 = name // 初回アクセス時だけinitializedがprintされる let name2 = name // 2回目以降は初回の評価結果のキャッシュが返却される // varなので再代入できる。 name = "fuga"