この記事について
この記事は、以前書いた「[Swift] Optional型についてのまとめ」の補足記事です。
開発環境
- Xcode 6 Beta6 (Playground)
- Mac OS X 10.9.3
Optional Chaining でアンラップしているはずなのに、アンラップされない
以下のコードを見てください。
Optional Chaining(? を使ってアンラップする方法)でアンラップしているはずなのに、アンラップされていないように見えます。
class Dog {}
var wrapped: Dog? = Dog() // Optional 型
var unwrapped: Dog = Dog() // 非 Optional 型
//
// 非 Optional 型に Optional 型は代入できない
//
unwrapped = wrapped // -> error: value of optional type 'Dog?' not unwrapped...
//
// Forced Unwrapping でアンラップすると、正常に代入できる
//
unwrapped = wrapped!
//
// しかし、Optional Chaining でアンラップすると、代入できない
//
unwrapped = wrapped? // -> error: value of optional type 'Dog?' not unwrapped...
Optional 型の変数を、非 Optional 型の変数に代入する際の挙動が、Forced Unwrapping(! を使う方法)と Optional Chaining(? を使う方法)で異なっています。
-
Forced Unwrapping(! を使う方法)でアンラップする場合 -> 正常に代入できる
-
Optional Chaining(? を使う方法)でアンラップする場合 -> エラーが出る(アンラップしていない、というエラーが出る)
Optional Chaining でアンラップしているはずなのに、「アンラップされていない」というエラーが出てしまいます。なぜでしょうか?
これは、
- アンラップした際の戻り値が、Forced Unwrapping と Optional Chaining で異なる
ためです。以降、詳しく見ていきます。
Optional Chaining は、戻り値をラップしてから返す
筆者は、Optional Chaining について、
- 一度アンラップしてメソッド等を呼び出したあとに、戻り値を再度ラップし直してから、返している
というイメージで捉えています。
以下の Dog クラスを例に、具体的に見ていきます。
class Dog {
func bark() -> String {
return "Wan!"
}
}
var wrapped: Dog? = Dog() // Optional 型
Forced Unwrapping(! を使う方法)の場合、戻り値は、素の型
アンラップ時の内部処理のイメージ その1
println(wrapped!) // -> __lldb_expr_11.Dog(= Dog 型)
上記のコードのアンラップ時の内部処理は、以下のようになっているものと推測されます。
- wrapped をアンラップする
- アンラップした値を「そのまま」返す
アンラップ時の内部処理のイメージ その2
println(wrapped!.bark()) // -> Wan!(= 非 Optionnal の String 型)
- wrapped をアンラップする
- bark() を呼び出す
- bark() の戻り値(非 Optional の String 型)を「そのまま」返す
Optional Chaining(? を使う方法)の場合、戻り値は、Optional 型
アンラップ時の内部処理のイメージ その1
println(wrapped?) // -> Optional(__lldb_expr_11.Dog) (= Optional の Dog 型)
- wrapped をアンラップする
- 再度、wrapped を「ラップし直してから」返す
アンラップ時の内部処理のイメージ その2
println(wrapped?.bark()) // -> Optional("Wan!") (= Optional の String 型)
- wrapped をアンラップする
- bark() を呼び出す
- bark() の戻り値(非 Optional の String 型)を「ラップしてから」返す。
上記のように、Optional Chaining の場合は、最終的な戻り値を Optional 型に「ラップしてから」返しています。
これは、Optional Chaining が、nil を返す可能性があるためです(詳細は、以前書いた記事を参照してください)。nil を許容するためには、戻り値は Optional 型でなければいけません。
補足(元々の戻り値が Optional 型だった場合、戻り値は Optional 型のまま)
元々の戻り値が Optional 型だった場合は、Optional Chaining の戻り値も、Optional 型のままになります。さらに、ラップするようなことはしません。
アンラップ時の内部処理のイメージ
//
// Dog 型に wrappedBark() を追加
//
extension Dog {
func wrappedBark() -> String? { // Optional の String 型を返す
return "Wrapped Wan!"
}
}
var wrapped: Dog? = Dog() // Optional 型
println(wrapped?.wrappedBark()) // -> Optional("Wrapped Wan!") (= Optional の String 型)
- wrapped をアンラップする
- wrappedBark() を呼び出す
- wrappedBark() の戻り値(Optional の String 型)を「ラップせずに、そのまま」返す。
注)
Optional 型をさらに Optional 型でラップした場合は、以下のような出力になります。
var i: Int?? = 1 // Optional 型の Optional 型
println(i) // -> Optional(Optional(1))
Optional Chaining と Optional Binding を組み合わせた場合
アンラップ時の内部処理のイメージ
if var unwrapped = wrapped?.bark() {
println(unwrapped) // -> Wan! (= 非 Optional の String 型)
}
[Optional Chaining] 1. wrapped をアンラップする
[Optional Chaining] 2. bark() を呼び出す
[Optional Chaining] 3. bark() の戻り値(非 Optional の String 型)を「ラップしてから」返す。
[Optional Binding ] 4. 上記 3. の値をアンラップしてから、unwrapped に代入する
Forced Unwrapping と Optional Binding を組み合わせた場合
アンラップ時の内部処理のイメージ
if var unwrapped = wrapped!.bark() { // -> error: bound value in a conditional binding must be of Optional type
}
Optional Binding では、代入する側の値(ここでは、wrapped!.bark())は Optional 型でなければなりません。上記のコードでは、wrapped!.bark() の戻り値が、非 Optional 型であるため、エラーが発生します。
[Forced Unwrapping] 1. wrapped をアンラップする
[Forced Unwrapping] 2. bark() を呼び出す
[Forced Unwrapping] 3. bark() の戻り値(非 Optional の String 型)を「そのまま」返す
[Optional Chaining] 4. 上記 3. の値をアンラップすることができないため、エラーが発生する
Implicitly Unwrapped Optional 型の場合、戻り値は、素の値
Implicitly Unwrapped Optional 型についても、見ていきます。
var iwrapped: Dog! = Dog() // Implicitly Unwrapped Optional 型
アンラップ時の内部処理のイメージ その1
println(iwrapped) // -> __lldb_expr_11.Dog (= Dog 型)
- iwrapped を「暗黙的に」アンラップする
- アンラップした値を「そのまま」返す
アンラップ時の内部処理のイメージ その2
println(iwrapped.bark()) // -> Wan! (= 非 Optional の String 型)
- iwrapped を「暗黙的に」アンラップする
- bark() を呼び出す
- bark() の戻り値(非 Optional の String 型)を「そのまま」返す
注意
上記の一連の内部処理のイメージは、筆者の推測によるものです。コンパイラの実装がどうなっているのかは不明です。
ただ、上記のように捉えておけば、アンラップ時の戻り値について、理解しやすいのではないかと思います。
まとめ
他のアンラップ方法と異なり、Optional Chaining (? を使う方法)は、一度アンラップしたあとに、最終的な戻り値を Optional 型にラップしてから返します。
Optional Chaining を使う際は、注意してください。
アンラップの方法 | 例 | 戻り値 |
---|---|---|
Forced Unwrapping | wrapped!.bark() | 素の型 |
Implicitly Unwrapped Optional 型 | wrapped.bark() | 素の型 |
Optional Chaining | wrapped?.bark() | Optional 型(最終的な戻り値を Optional 型にラップする) |
参考文献
以下の 1. のブログ記事(「疑問」の項目)が、今回の記事を書くきっかけになりました。ありがとうございました。