オプショナル列挙型
Swiftでは、値が存在しない可能性を示すための列挙型としてオプショナル列挙型が提供されています。型や値の後ろに「?」や「!」などの記号を付けることで、型や値をオプショナル列挙型にくるんだり、中の値を取り出したりできます。
オプショナル列挙型は、次のようにメンバーが宣言されています。
enum Optional<T> {
case None
case Some(T)
}
Noneメンバーは、値が存在しないことを明示するために用います。Some(T)のメンバーは、メンバーにTを付随型として与えたもので、値が存在することを明示するために用います。
以降では、オプショナル列挙型を扱うのに便利な記法である「?」「!」についての解説を交えながら、全体のルールを見ていきます。
Objective-Cでは値がないことを示すのに、nilキーワードやNSNull、NSNotFoundなど、さまざまな選択肢がありました。Swiftでは、値がないかもしれないという状況を表すための方法として、オプショナル列挙型が用意されています。値がないかもしれないという状況に対しては同一の型で対応出来る為に、より挙動がわかりやすいコードが書きやすくなったと言えそうです。
なお、Swiftのnilは、Objective-Cのnilとは異なり、ポインタではありません。オプショナル列挙型と併用する場合はOptional.Noneとみなされます。
以降はオプショナル列挙型との併用を前提とした上で話を進めますので、Optinal.Noneをnilと略記します。
「?」のルール
既存の型Tに「?」を加えるだけで、その型はOptional<T>として扱われます。例えば、Int?型の定数宣言の実体は、Optional<Int>の定数宣言とみなされます。
let someNumber: Int? = Optional.Some(1) // Some 1 ← Someはオプショナル列挙型を表す
以降の説明では略記のために、Optional<T>をT?と記述します。
T?型の変数にはT?型のみならず、T型の変数も代入できます。
var someValue: Int? = 1 // Some 1
このコードからは、代入時、自動的に型変換が行われることがわかります。
オプショナルチェーン
「T?型」であるメソッドやプロパティの返り値に対し、「T型」に定義されたメソッドやプロパティをピリオドの連鎖で呼び出したいという場面は、たびたび発生します。そのときに役立つのが、オプショナルチェーン注1と呼ばれる機能です。
注1:ある型に定義されたメソッドやプロパティを「.」(ピリオド)つなぎで呼び出していくことを、JavaScript等多くの言語ではチェーンと呼んでいます。
例えば、String型に用意されているtoInt()メソッドはInt?型を返します。このInt?型の返り値に対し、Int型に定義されているdescription()メソッドを呼び出したいとしましょう。ピリオドをつなげるだけのチェーンではうまくいきません。
let numStr: String = "134" numStr.toInt().description() // エラー
しかし、「.」の前に「?」を置くことでそれが可能になります。この仕組みがオプショナルチェーンです。
let numStr: String = "134" numStr.toInt()?.description() // Some "134"
次のコードを見てください。もともと返り値がString型であったdescription()メソッドがString?型を返して("134"ではなくSome "134"を返して)います。
let numStr: String = "134" numStr.toInt()?.description() // Some "134"
このように、もとはT型の値を返すメソッドやプロパティでも、オプショナルチェーンを使うと最終的な返り値はT?型となります。ただし、返り値がT?型のメソッドやプロパティの戻り値は(T?)?型となったりせず、T?型のままです。
オプショナルチェーンの途中にnilを返すメソッドやプロパティがあった場合、それ以降のメソッドやプロパティは実行されず、最終的にnilを返します。
次のコードはそのことを示す例です。numStr.toInt()はSome(124)を返しますが、notNumStr.toInt()はnilを返します(文字列"12s"は数値に変換できない)。そのため、notNumStr.toInt()に続くprintSelf()メソッドは実行されず、出力もありません。
extension Int {
func printSelf() {
print(self)
}
}
let numStr: String = "124"
let notNumStr: String = "12s"
numStr.toInt()?.printSelf() // 124
notNumStr.toInt()?.printSelf() // 何も表示しない
「!」のルール
Swiftでは既存の型Tに「!」を付けるだけで、ImplicitlyUnwrappedOptional<T>注2として扱われます。こちらもT?と同様に、以降は「T!」と略記して解説していきます。
ImplicitlyUnwrappedOptionalを強いて日本語に訳すとしたら暗黙的アンラップオプショナルといった具合でしょうか。オプショナル列挙型の値をを代入することで中身の値を特に明示せずに取り出すことができます。
まず、T!型にはT型とT?型の双方を代入可能です。TをIntとした例を見てみましょう。
var numStr: Int! = 123 numStr = "1s3".toInt()
この場合、"1s3"にtoInt()を適用して得られる値はnilですが、代入できています。
また、T?型の値の後ろに「!」を適用すると、次のように動作します。
- T?がSome(T)の場合には、中身のT型の値を取り出せる。
- T?がNoneの場合には、ランタイムエラーが起こる。
今度は、TがStringとIntである場合を見てみましょう。
var notNumStr: String? = "1s3" // Some "1s3" var notNum: Int? = notNumStr.toInt() // nil notNumStr! // "1s3" var notInt: Int = notNum! // runtime error
notNumStr変数の値はOptional.Some("1s3")であるため、「!」を適用すると中身の"1s3"が取り出せます。一方、notNum変数の値はnilであるため、「!」を適用するとランタイムエラーが起こります。T?に対する「!」の適用は、変数の値がnilでないとわかっている場合のみに限定したほうがよいでしょう。
