LoginSignup
32
33

More than 5 years have passed since last update.

[Swift][Optional] アンラップしているはずなのに、アンラップされない

Last updated at Posted at 2014-08-22

この記事について

この記事は、以前書いた「[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 型)

上記のコードのアンラップ時の内部処理は、以下のようになっているものと推測されます。

  1. wrapped をアンラップする
  2. アンラップした値を「そのまま」返す

アンラップ時の内部処理のイメージ その2

println(wrapped!.bark()) // -> Wan!(= 非 Optionnal の String 型)
  1. wrapped をアンラップする
  2. bark() を呼び出す
  3. bark() の戻り値(非 Optional の String 型)を「そのまま」返す

Optional Chaining(? を使う方法)の場合、戻り値は、Optional 型

アンラップ時の内部処理のイメージ その1

println(wrapped?) // -> Optional(__lldb_expr_11.Dog) (= Optional の Dog 型)
  1. wrapped をアンラップする
  2. 再度、wrapped を「ラップし直してから」返す

アンラップ時の内部処理のイメージ その2

println(wrapped?.bark()) // -> Optional("Wan!") (= Optional の String 型)
  1. wrapped をアンラップする
  2. bark() を呼び出す
  3. 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 型)
  1. wrapped をアンラップする
  2. wrappedBark() を呼び出す
  3. 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 型)
  1. iwrapped を「暗黙的に」アンラップする
  2. アンラップした値を「そのまま」返す

アンラップ時の内部処理のイメージ その2

println(iwrapped.bark()) // -> Wan! (= 非 Optional の String 型)
  1. iwrapped を「暗黙的に」アンラップする
  2. bark() を呼び出す
  3. bark() の戻り値(非 Optional の String 型)を「そのまま」返す

注意

上記の一連の内部処理のイメージは、筆者の推測によるものです。コンパイラの実装がどうなっているのかは不明です。

ただ、上記のように捉えておけば、アンラップ時の戻り値について、理解しやすいのではないかと思います。

まとめ

他のアンラップ方法と異なり、Optional Chaining (? を使う方法)は、一度アンラップしたあとに、最終的な戻り値を Optional 型にラップしてから返します。

Optional Chaining を使う際は、注意してください。

アンラップの方法 戻り値
Forced Unwrapping wrapped!.bark() 素の型
Implicitly Unwrapped Optional 型 wrapped.bark() 素の型
Optional Chaining wrapped?.bark() Optional 型(最終的な戻り値を Optional 型にラップする)

参考文献

以下の 1. のブログ記事(「疑問」の項目)が、今回の記事を書くきっかけになりました。ありがとうございました。

  1. NTEC - 【Swift入門】Optional型 (?, !) をまとめてみた

  2. The Swift Programming Language

32
33
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
33