Help us understand the problem. What is going on with this article?

[Swift] Optional 型についてのまとめ Ver2

More than 5 years have 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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした