More than 1 year has passed since last update.

はじめに

  • 以前の記事は一部内容が古くなったので、全面的に書き直しました

環境

  • 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 型の変数をアンラップする方法として、以下のものがある。

  1. Forced unwrapping
  2. Optional chaining
  3. Optional binding
  4. 比較演算子

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!")

上記の命令では以下の処理が行われている。

  1. wrappedDog をアンラップする
  2. bark() メソッドを呼ぶ
  3. bark() メソッドが String 型を返す
  4. String 型を optional 型にラップする
  5. 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

ifwhile 文の条件式で宣言され、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
}

wrappedDognil だった場合、条件式は 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 をアンラップしたもの) を返す。

aがnilでない場合
var a: Int? = 1

//
// a は nil でないので、a をアンラップしたものを返す
//
println(a ?? 2) // -> 1

その2) a が nil であれば、b を返す。

aがnilの場合
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

Wikipedia - Swift (プログラミング言語)

Swiftのnilがなるほどわからん

Swiftのnil-∞よりも小さい ??

Swiftの比較演算子を使うとき,変数は自動でアンラップされる。

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

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

@dankogai さんと @_shingt さんの tweet