はじめに
- 以前の記事は一部内容が古くなったので、全面的に書き直しました
環境
- Xcode 6.1 GM seed 2
- iOS Simulator (iPhone6 / iOS 8.1)
Optional 型とは?
- Optional 型 -
nil
の代入を許す - 非 optional 型 -
nil
の代入を許さない
Optional 型の宣言方法
-
Int
型
var a: Int? // Optional 型
var b: Int // 非 optional 型
-
String
型
var a: String? // Optional 型
var b: String // 非 optional 型
T?
は Optional<T>
のシンタックスシュガーである
var a: Int?
var b: Optional<Int> // Int? と同じ意味
Optional<T>
は enum
型である
Optional<T>
は、標準ライブラリの中で enum
型として定義されている。
enum Optional<T> : Reflectable, NilLiteralConvertible {
case None // nil に相当する
case Some(T) // T 型(素の型)の値が入る(Int 型や String 型の値など)
...
}
Optional 型は nil
が代入されるのを許す
var a: Int? // Optional 型
a = nil // -> OK
非 optional 型に nil
を代入しようとすると、コンパイラエラーが発生する。
var b: Int // 非 optional 型
b = nil // -> Compiler error: Type 'Int' does not conform to protocol 'NilLiteralConvertible'
Optional 型の初期値は nil
var a: Int? // Optional 型
println(a) // -> nil
非 Optional 型の場合、初期値には何も入っていない(nilも入っていない)。
var b: Int // 非 optional 型
println(b) // -> Compiler erorr: Variable 'b' used before being initialized
Optional 型は、非 Optional 型と同じようには扱えない
var a: Int = 1
println(a + 2) // -> 3
var b: Int? = 1
println(b + 2) // -> Compiler error: Value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?
上記のとおり、Int?
型は、Int
型とは別の型なので、同じように操作することはできない。Int
型と同じように操作するためには、変数を「アンラップ」する必要がある(後述)。
ラップ(wrap)とアンラップ(unwrap)
「ラップされている」とは?
Optional 型(Optional<T>
型)の変数のことを「ラップされている」と言う(おそらく、Optional<T>
型に T
型の変数が「包まれている」ことに由来すると思われる)。
「アンラップする」とは?
Optional 型(Optional<T>
型)から T
型の変数を取り出すことを、「アンラップする」と言う。
Optional 型をアンラップする方法
Optional 型の変数をアンラップする方法として、以下のものがある。
- Forced unwrapping
- Optional chaining
- Optional binding
- 比較演算子
1. Forced Unwrapping
ここでは、以下の Dog クラスを例に説明を進める。
class Dog {
func bark() -> String {
return "Wan!"
}
}
var wrappedDog: Dog? = Dog() // Optional 型
//
// アンラップをしないと、bark() メソッドを呼び出すことができない
//
println(wrappedDog.bark()) // -> Compiler error: 'Dog?' does not have a member named 'bark'
「!」 を optional 型の変数の後ろに付けるとアンラップすることができる。これを Forced unwrapping と言う。
//
// Forced unwrapping
//
println(wrappedDog!.bark()) // -> Wan!
Forced unwrapping でnil
をアンラップしようとすると、ランタイムエラーが発生する。
wrappedDog = nil
println(wrappedDog!.bark()) // -> Runtime error: unexpectedly found nil while unwrapping an Optional value
2. Optional Chaining
「?」 を使ってアンラップすることを optional Chaining と言う。
//
// Optional Chaining
//
println(wrappedDog?.bark()) // -> Optional("Wan!")
Optional chaining で nil
をアンラップしようとすると、nil
が返ってくる。
wrappedDog = nil
println(wrappedDog?.bark()) // -> nil
注意: Optional Chaining は Optional 型を返す
Forced unwrapping はアンラップをした後に、最終的な戻り値をそのまま返す。以下の場合、bark メソッドの戻り値(String 型)をそのまま返す。
//
// Forced unwrapping
//
println(wrappedDog!.bark()) // -> Wan!
Optional chaining はアンラップをした後に、最終的な戻り値を optional 型にラップしてから返す。以下の場合、bark メソッドの戻り値(String 型)を optional 型にラップしてから返す。
//
// Optional chaining
//
println(wrappedDog?.bark()) // -> Optional("Wan!")
上記の命令では以下の処理が行われている。
-
wrappedDog
をアンラップする -
bark()
メソッドを呼ぶ -
bark()
メソッドがString
型を返す -
String
型を optional 型にラップする -
Optional<String>
型を返す
Optional Chaining が Optional 型を返す理由
これは、optional chaining が nil
を返す可能性があるためである。アンラップ対象の変数が nil
だった場合、optional chaining はランタイムエラーを起こさずに nil
を返す。nil
を許容するためには、戻り値は optional 型でなければならない。
注意:「?」には二つの別の意味がある
以下は「optional 型の宣言」という意味である。
var a: Int? // Optional 型
一方で、以下は「optional chaining」を意味する。
//
// Optional chaining
//
println(wrappedDog?.bark()) // -> Optional("Wan!")
どちらも「?」を使っているが、意味が異なるので混同しないように注意。
3. Optional Binding
if
や while
文の条件式で宣言され、optional 型の変数を代入された変数は、非 optional 型になる。これを「optional binding」と言う
var wrappedDog: Dog? = Dog() // Optional 型
//
// Optional binding
//
if var unwrappedDog = wrappedDog { // unwrappedDog はアンラップされた値(素の Dog 型)になる
//
// unwrappedDog は非 optional 型なので、アンラップは不要
//
println(unwrappedDog.bark()) // -> Wan!
}
let
も使用可能。
if let unwrappedDog = wrappedDog {
println(unwrappedDog.bark()) // -> Wan!
}
while
文でも使用可能。
while var unwrappedDog = wrappedDog {
println(unwrappedDog.bark()) // -> Wan!
break
}
wrappedDog
が nil
だった場合、条件式は false
になる。
wrappedDog = nil
if var unwrappedDog = wrappedDog { // false
println("This is not printed out.")
}
4. 比較演算子
比較演算子を使うと、optional 型の変数が自動的にアンラップされる。
var wrappedInt: Int? = 1 // Optional 型
//
// 自動的にアンラップされる
//
println(wrappedInt == 1) // -> true
println(wrappedInt >= 1) // -> true
println(wrappedInt > 1) // -> false
println(wrappedInt <= 1) // -> true
println(wrappedInt < 1) // -> false
println(wrappedInt != 1) // -> false
Optional 型の変数を比較演算子の右に置いてもかまわない。
println(1 == wrappedInt) // -> true
Optional 型の変数が nil
であっても、エラーは発生しない。
wrappedInt = nil
println(wrappedInt == 1) // -> false
注意: nil
は負の無限大より小さい
nil
は、負の無限大より小さい。
println(nil < -Double.infinity) // -> true
これが奇妙な結果を招くことがあるので注意。
var wrappedDouble: Double? = nil
if wrappedDouble < 0.0 {
println("\(wrappedDouble) is a negative number.") // -> nil is a negative number.
}
Implicitly Unwrapped Optional 型とは?
「Implicitly unwrapped optional 型」 とは、「自動的にアンラップされる optional 型」のことである。
Implicitly Unwrapped Optional 型の宣言方法
-
Int
型
//
// 「?」ではなく、「!」を使う
//
var a: Int! // Implicitly unwrapped optional 型
var b: Int? // Optional 型
-
String
型
var a: String! // Implicitly unwrapped optional 型
var b: String? // Optional 型
T!
は、ImplicitlyUnwrappedOptional<T>
のシンタックスシュガーである
var a: Int!
var b: ImplicitlyUnwrappedOptional<Int> // Int! と同じ意味
ImplicitlyUnwrappedOptional<T>
は enum
型である
ImplicitlyUnwrappedOptional<T>
は標準ライブラリの中で enum
型として定義されている。
enum ImplicitlyUnwrappedOptional<T> : Reflectable, NilLiteralConvertible {
case None // nil に相当する
case Some(T) // T 型(素の型)の値が入る(Int 型や String 型の値など)
...
}
Implicitly Unwrapped Optional 型の初期値は nil
var a: Int! // Implicitly unwrapped optional 型
println(a) // -> nil
Implicitly Unwrapped Optional 型は自動的にアンラップされる
var iwrappedDog: Dog! = Dog() // Implicitly unwrapped optional 型
//
// 「!」や「?」を付けなくても、自動的にアンラップされる
//
println(iwrappedDog.bark()) // -> Wan!
nil
をアンラップしようとすると、ランタイムエラーが発生する。
iwrappedDog = nil
println(iwrappedDog.bark()) // -> Runtime error: unexpectedly found nil while unwrapping an Optional value
注意: 「!」には二つの別の意味がある
以下は「Implicitly unwrapped optional 型の宣言」という意味である。
var a: Int! // Implicitly unwrapped optional 型
一方で、以下は「Forced unwrapping」を意味する。
var wrappedDog: Dog? = Dog() // Optional 型
//
// Forced unwrapping
//
println(wrappedDog!.bark()) // -> Wan!
どちらも「!」を使うが、意味が異なるので混同しないように注意。
Optional 型の代入演算子
以下の変数をもとに説明を進める。
var wrapped: Int? = 1 // Optional 型
var iwrapped: Int! = 2 // Implicitly unwrapped optional 型
var unwrapped: Int = 3 // 非 optional 型
Optional 型の代入
Optional 型は、implicitly unwrapped optional 型に代入できる。
iwrapped = wrapped // OK
Optional 型は、非 optional 型に**「代入できない」**。
unwrapped = wrapped // Compiler error: Value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?
Forced unwrapping でアンラップされた optional 型は、非 optional 型に代入できる。
unwrapped = wrapped! // OK
Optional chaining でアンラップされた optional 型は、非 optional 型に**「代入できない」**(optional chaining の戻り値が optional 型であるため)。
unwrapped = wrapped? // Compiler error: Value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?
Implicitly Unwrapped Optional 型の代入
Implicitly unwrapped optional 型は、optional 型に代入できる。
wrapped = iwrapped // OK
Implicitly unwrapped optional 型は、 非 optional 型に代入できる(暗黙的にアンラップされていることに注意)。
unwrapped = iwrapped // OK
非 Optional 型の代入
非 optional 型は、optional 型に代入できる。
wrapped = unwrapped // OK
非 optional 型は、implicitly unwrapped optional 型に代入できる。
iwrapped = unwrapped // OK
?? 演算子
a ?? b
は、以下の処理を行う。
その1) a が nil
でなければ、a!
(a をアンラップしたもの) を返す。
var a: Int? = 1
//
// a は nil でないので、a をアンラップしたものを返す
//
println(a ?? 2) // -> 1
その2) a が nil
であれば、b を返す。
a = nil
//
// a は nil なので、2 を返す
//
println(a ?? 2) // -> 2
a ?? b
は、a != nil ? a! : b
のシンタックスシュガーである
以下の2つは同じ意味である。
var a: Int? = 1
println(a ?? 2) // -> 1
println(a != nil ? a! : 2) // -> 1
まとめ
Optional 型と Implicitly Unwrapped Optional 型
型 | 宣言方法 | 型の実体(enum 型) |
アンラップ操作は必要か? |
---|---|---|---|
Optional 型 | var a: T? |
Optional<T> |
必要 |
Implicitly unwrapped optional 型 | var a: T! |
ImplicitlyUnwrappedOptional<T> |
不要(自動的にアンラップされる) |
nil
をアンラップしようとしたときの挙動
型 | アンラップの方法 |
nil をアンラップしたときの結果 |
---|---|---|
Optional 型 | Forced unwrapping (e.g. wrappedDog! ) |
ランタイムエラー |
Optional 型 | Optional chaining (e.g. wrappedDog? ) |
nil を返す |
Implicitly unwrapped optional 型 | 自動的にアンラップされる | ランタイムエラー |
アンラップ時の戻り値
型 | アンラップの方法 | 戻り値 | 例 |
---|---|---|---|
Optional 型 | Forced Unwrapping | 最終的な戻り値をそのまま返す | wrappedDog!.bark() // -> String 型 |
Optional 型 | Optional Chaining | 最終的な戻り値を optional 型にラップしてから返す | wrappedDog?.bark() // -> Optional<String> 型 |
Implicitly Unwrapped Optional 型 | 自動的にアンラップされる | 最終的な戻り値をそのまま返す | iwrappedDog.bark() //-> String 型 |
参考文献
The Swift Programming Language
Facets of Swift, Part 1: Optionals
stakoverflow - What does an exclamation mark mean in the Swift language?
Swiftの比較演算子を使うとき,変数は自動でアンラップされる。
【Swift入門】Optional型 (?, !) をまとめてみた