[Swift 基本の型 第4回(第2部)] Optional型

f:id:KAFAppFactory:20200622064217j:plain

Swiftの基本の型 第4回 第2部になります。前回のOptional型の続きです。頑張りましょう!
本記事は「Swift実践入門」を参考にしています。

gihyo.jp

Optional型のケースについて

SwiftではOptionalはenumという列挙型を用いて定義されています。 列挙型は、複数の識別子をまとめる型で、それぞれの識別子をケースといいます。 Optional型では、.none.someのケースがあり、enumでは列挙されたケースのうち、どれかを一つを値として持つため、.none.someを値として持ちます。

.none
値が存在しないことを表すケースになります。 値が存在しないことを表すケース.noneはOptional\.noneで生成し、 簡略化した表記にしますと、Optional\.noneはnilと表記できます。

.some
値が存在することを表すケース.someはOptional\.some(値)で生成します。 これらの値には簡略化された表記が存在し、 Optional\.some(値)はOptional(値)と表記可能です。

以上をまとめると次の表のようになります。

ケース .none .some
不在 存在
簡略化表記 nil Optional(値)

また、列挙型のケースとしてOptional型の.someの値を生成する場合、Int型やString型は.someに持たせる値から型推論が可能です。 型推論を利用すると、.someの生成は\を省略して次のように表記可能です。

let some = Optional.some(1) // Optional<Int>型

一方で.noneを生成する場合は型推論のもととなる値が存在しないため、そのままでは\を省略できません。

let none: Int? = Optional.none // Optional<Int>型

上記の例のように.someの生成では値から型推論ができる一方で、.noneの生成では型を明示する必要があります。

オプショナルチェイン

前回はアンラップの方法について3つの方法を用いて、Optional型の値を取り出す方法を確認しましたが、 今回はオプショナルチェインというアンラップを伴わずに値のプロパティやメソッドにアクセスする方法について確認します。

オプショナルチェインを用いずに、Optional\型からWrapped型のプロパティにアクセスするには、いったんオプショナルバインディングなどによってアンラップする必要がありました。

復習になりますが、次の例1ではOptional\型からDouble型のisInFiniteプロパティにアクセスするために、オプショナルバインディングを行っています。例2ではオプショナルチェインを用いてプロパティにアクセスしています。

例1 ) オプショナルバインディング

let optionalDouble = Optional(1.0) // 1
let optionalIsInFinite: Bool?
if let double = optionalDouble {
    optionalIsInFinite = double.isInfinite
} else {
    optionalIsInFinite = nil
}

print(String(describing: optionalIsInFinite))

例2 ) オプショナルチェイン

let optionalDouble2 = Optional(1.0) // Optional(1.0)
let optionalIsInFinite2 = optionalDouble2?.isInfinite

print(String(describing: optionalIsInFinite2))

例1と例2を比較してもらえばわかる通り、オプショナルチェインを用いた場合だとコードが少なくてすみますね!
例のようにオプショナルチェインを用いれば、アンラップを伴わずにWrapped型のプロパティやメソッドにアクセスできます。オプショナルチェインを利用するには、Optional\型の式の後、?に続けてWrapped型のプロパティ名やメソッド名を記述します。

オプショナルチェインでは、Optional\型の変数や定数がnilだった場合、?以降に記述されたプロパティやメソッドへのアクセスは行わずに、nilが返却されます。 元のOptional\型の式が値を持っていないということは、アクセス対象のプロパティやメソッドも存在しないということであり、返すべき値が存在しないからです。

map(_:)メソッドとflatMap(_:)メソッド

map(_:)メソッドとflatMap(_:)メソッドは、アンラップを伴わずに値の変換を行うメソッドです。 map(_:)メソッドの引数には、値を変換するクロージャを渡します。

クロージャとは、波カッコ {} のブロックで囲んだ中にある処理を実行する自己完結型の機能のことです。関数と似ていますね。メリットとしては関数のようにいちいち名前をつけなくても良いという点になります。

map(_:)メソッドでは、Wrapped型の値が存在すればクロージャを実行して値を変換し、値が存在しなければ何も行わないという処理になります。 次の例では、Int?型の定数に対して値を2倍にするクロージャを実行し、結果としてInt?型のOptional(34)を受け取っています。

let d = Optional(10)
let e = d.map({ value in value * 2 }) // 20
type(of:e) // Optional<Int>.Type

また、map(_:)メソッドを用いて、別の型に変換することもできます。 次の例では、Int?型の定数に対してString型に変換するクロージャを実行し、結果としてString?型の値Optional(17)を受けとっています。

let f = Optional(10)
let g = f.map({ value in String(value) }) // "10"
type(of:g) // Optional<String>.Type

flatMap(_:)メソッドもmap(_:)型と同様に値を変換するクロージャを引数にとりますが、クロージャの戻り値はOptional\型です。下記の例からその違いを見ていきましょう!

例1 ) flatmapメソッド

let h = Optional("10")
let i = h.flatmap({ value in Int(value) }) // 10
type(of:i) // Optional<Int>.Type

例2 ) mapメソッド

let j = Optional("10")
let k = j.map({ value in Int(value) }) // 10
type(of:k) // Optional<Optional<Int>>.Type

上記の例では、String?型の定数に対してInt?型に変換するクロージャを実行し、Int?型の値Optional(17)を受けとっています。

もし、ここでflatMap(_:)メソッドではなくmap(_:)メソッドを使用してしまうと、最終的な結果は二重にOptional\型に格納されているInt??型となってしまいます。 ここでのポイントは、flatmap(_:)はmapをした上でアンラップしてくれることです。

暗黙的アンラップされたOptional\型

暗黙的アンラップは、 次のような書き方で定義します。

var A: Int!
A = 1

ここで、定義した際の初期値はnilになります。

この定義によって生成されたOptional\型の値は、値へのアクセス時に自動的に強制アンラップを行うため、暗黙的アンラップされたOptional\型と言います。

暗黙的アンラップされたOptional\型は、アクセス時に毎回強制アンラップが行われるため、Wrapped型と同様に扱えますが、アクセス時にnilであった場合は実行時エラーが発生します。 次の例では、Int!型の値とInt型の値の間で演算を行っている。定数aには値が存在するため演算が成功しますが、変数bには値が存在しないため実行時エラーとなります。

let p: Int! = 1
p + 1 // Int型と同様に演算が可能

var q: Int! = nil
q + 1 //値が入っていないため実行時エラー

Optional\型の強制アンラップと同じ理由で使うのは避けたほうが良いかもしれません。。。

終わりに

今回、Swift 基本の型 第4回第2部としてOptional型のうち、オプショナルチェインとmapメソッドについてまとめました。他にも暗黙的アンラップについてもまとめましたので、アプリを作っていく上でわからなくなった際は第4回を読み直して再度理解を深めましょう!