Swiftのオプショナル型(Optionals)

オプショナル

ここではオプショナル(Optionals)に関して、基本的な使い方を説明します。また、オプショナルと密接に関連するnilという特殊な値に関しても簡単に紹介します。

オプショナルは、これまで紹介してきたような基本的な型(Intなど)とはちょっと使い方が異なります。したがって、最初は面食らうかもしれませんが、Xcodeのplaygroundなどで色々試してみれば、使い方に慣れるのではないかと思います。

オプショナル(Optionals)とは?

Optionalsの文章による定義

Swiftにおけるオプショナル(Optionals)とは何でしょうか?文章で定義すると、

Optionalsの定義
ある値が割り当てられていて、かつoptionalsを外して(unwrap)値を取り出すことが出来る
or
値が存在しない

となります。文章で書くと、いまいち良く分からない気がします。Optionalsの振る舞いを理解するには、実際に簡単なプログラムをXcode上で動かしてみるのがてっとり早いです。

Optionalsの振る舞い

実際に、以下のサンプルコードで見てみましょう。

//StringをIntに明示的型変換しようとする場合
let korewaNumberDesuka = "2015"
let tabunNumberDesu = Int(korewaNumberDesuka)
// 2015と表示

2行目の初期化は、うまくいかない可能性があります。今回のケースでは、たまたま2015という数字のStringでしたので、Intへの型変換によって整数の2015に変換されました。

しかし、例えばkorewaNumberDesuka"hello world!"で初期化した場合はどうでしょうか?実際にやってみると分かりますが、nilという値を返します

//上記の例で「2015」を「hello world!」に変更
let korewaNumberDesuka = "hello world!"
// hello world!と表示
let tabunNumberDesu = Int(korewaNumberDesuka)
// nilと表示

この次で紹介しますが、このnilというのが「値がないよ」という特殊な状態を示す値です。

上記の例のように、

最終的に値があるかどうか良く分からない場合に使われるのがオプショナル(Optionals)

になります。

Optionalsの宣言方法

Optionals型を表現する場合、型の後ろにクエスチョンマーク?を付けます。例えば、オプショナル整数を表現する場合、Int?と書きます。後ろのクエスチョンマークがポイントです。このInt?

もしかしたら何かしらの整数Intの値を持ってるかもしれないし、何の値も持っていない(nil)かもしれない

ということを表しています。

OptionalsはObject-C(C言語も)にはない

Optionalsの概念は、どうやらObject-CやCにはないようです。以下公式マニュアルより抜粋

“NOTE
The concept of optionals doesn’t exist in C or Objective-C. The nearest thing in Objective-C is the ability to return nil from a method that would otherwise return an object, with nil meaning “the absence of a valid object.” However, this only works for objects—it doesn’t work for structures, basic C types, or enumeration values. For these types, Objective-C methods typically return a special value (such as NSNotFound) to indicate the absence of a value. This approach assumes that the method’s caller knows there is a special value to test against and remembers to check for it. Swift’s optionals let you indicate the absence of a value for any type at all, without the need for special constants.”

抜粋:「The Swift Programming Language」、”The Basics – Optionals”より

最後の1文「Swiftの場合、特別な定数を用意しなくても、optionalsはどんな型に対しても”値がないよ”ということ知らせてくれる」が一番重要でしょうか。「どんな型でも」というのがポイントで、基本的な型であるInt等にも適用されるのが良いですね。

Optionalsが発生するのはStringから数値型に型変換する時

色々試してみた結果、基本的な型の場合、optionalsが発生するのはStringから数値型(IntDoubleなど)に型変換する時のようです。下に示したのが実行例です

// optionalsのテスト
let thisIsString = "string desu"
let thisMightBeInt = Int(thisIsString) // nil
let thisMightBeDouble = Double(thisIsString) // nil
let thisMightBeBool = Bool(thisIsString) // compile error

// simple type cast
let pi = 3.14
var piInt = Int(pi) // double --> int (3)
var piString = String(pi) // double --> string ("3.14")
//var piBool = Bool(pi) // double --> bool (true) (Swift 3ではコンパイルエラー)
var boolTest1 = Bool(-1) // int --> bool (true)
var boolTest2 = Bool(0) // int --> bool (false)
var someString1 = String(boolTest1) // bool --> string ("true")
var someString2 = String(boolTest2) // bool --> string ("false")

StringBoolには変換できないようですね。

型変換でおもしろいのはBoolで、0がfalse、それ以外の数値がtrueにキャスト(cast)されます。また、BoolStringにcastすると、truefalseがそのまま文字列になります。

クラス等の独自型に関しては、ここでは考慮していません。例えば、独自型の持つ内部変数(プロパティと呼びます)等の条件によって、「これこれこういう条件の場合はnilにする」という感じで独自型のoptionalsを設定することも可能です。これを説明するには、独自型(クラスや構造体など)と初期化に関しての知識が必要なので、詳しくは「Swiftをもっと深く学ぶ」の「失敗可能な初期化子(failable initializers)」で説明する予定です。

nilとは?

先ほど紹介したように、nilというのは「値がない」という状態を示す特別な値です。nilはoptional変数にのみ代入可能です。

//nilを代入
var yourName: String? = "My name" // optional String
// yourNameはStringの"My Name"
yourName = nil
// yourNameはnil

var yourEmailAddress = "My email" // String
yourEmailAddress = nil
// コンパイルエラー。nilはoptionalsでないと代入できない

ちなみに、optionalsを初期化なしで宣言した場合、初期値として自動的にnilがセットされます。

var oneHalf: Double?
print(oneHalf)
// "nil"と表示、oneHalfには自動的にnilがセットされる

Optionalsに値が入っているかどうか、どうやって確認する?

2つの方法があるので、それぞれ順番に紹介します。ここで紹介するのは

  • Forced Unwrapping
  • Optional Binding

です。

If文でnilかどうか判定

Optionalsが実際に値を持っているかどうかを判定するのに、制御構文の一つif文が使えます。

var yourName: String? = "My name"
if yourName != nil {
  print("yourNameは何か文字列を持ってます")
}
// "yourNameは何か文字列を持ってます"と表示

ここで!=は「等しくない(not equal)」ということを表す演算子(operator)です。この例では、optionalsとして宣言したyourNameという変数が、「nilと等しくない」という条件を満たした場合に、文字列をプリントしています。

Forced Unwrapping

上記の例のように、一度optionalsが値を持っていることを確認できれば、optionals変数の後にビックリマーク!(exclamation mark)をつけることで、型を確定することができます(optionalsを外す)。

これをForced Unwrappingと呼びます(うまい日本語訳が思いつきません)。

//OptionalsのForced Unwrapping
if yourName != nil {
  print("yourNameは\(yourName!)という文字列を持ってます")
}
// "yourNameはMy nameという文字列を持ってます"と表示

上記の例では、yourName!でForced Unwrappingし、yourName変数をStringとして表示しています。

公式マニュアルにも注意書きがあるように、!を使ってnil(値がない状態)にアクセスしようとすると、実行時エラー(runtime error)を起こすようです。試してみましょう。

//nilのForced Unwrapping
var thisIsNil: String? = "My name"
thisIsNil = nil
print("thisIsNilは\(thisIsNil!)という文字列を持ってます")
// 最終行でエラー、Xcodeは以下のようなエラーを表示します
//  fatal error: unexpectedly found nil while unwrapping an Optional value

Forcedは「無理やり」とか「強制的に」という意味ですから、Forced Unwrappingはまさに強制的にoptionalsを解除すると理解できます。!を使用したForced Unwrappingを使う場合には、確実に値がある(nilではない)ということを確認すべきですね。

Optional Binding

Optional Bindingの定義構文

Optional Bindingを言葉で表現すると、

  • Optionalsが値を持っているかどうかのチェック
  • もし値を持っていた場合、それを取り出して変数または定数として使えるようにする

という処理を一度に実行する方法、です。

実際の構文としては、if文を使う場合には以下のようになります

if let constantName = someOptional {
    statements
}

また、optional bindingはwhile文でも使えるようです。

Optional bindingの使用例

では実際にやってみましょう。

//Optional bindingの利用例
let possibleYear = "2015"
if let year = Int(possibleYear) {
  print("possibleYearは整数値\(possibleYear)を持っていて、yearという定数にその値\(year)を代入しました。")
}
else{
  print("possibleYearは整数に変換できませんでした")
}
// "possibleYearは整数値2015を持っていて、yearという定数にその値2015を代入しました。"と表示

この場合、Forced Unwrappingで使用した!は必要なく、yearの値を出力するのにそのまま\(year)と指定すれば良いことが分かります。ここで幾つか注意するポイントがあります。

  • yearは最初のif文内でのみ使用可能なローカル定数なので、例えばelse文の方でyearを使用できません。
  • 上記の例ではyearを定数としましたが、変数を利用することも可能です。

Optional bindingと条件判定を組み合わせる場合

また、複数のoptional bindingsを一つのif文に含めたり、条件判定を組み合わせることが可能です。例えば

if let twentyFourteen = Int("2014"), let twentyFifteen = Int("2015"),
    twentyFourteen < twentyFifteen {
        print("twentyFourteen = \(twentyFourteen)")
        print("twentyFifteen = \(twentyFifteen)")
}
//"twentyFourteen = 2014"
//"twentyFifteen = 2015"
//と表示

のような記述が可能です。Optional bindingや条件判定はコンマで接続することが出来ます。

Optional bindingも条件判定だと思って、上記の要素1つずつに対してif文を割り当てると、

// 複数のoptional binding、条件判定を個別に書くことも可能
if let twentyFourteen = Int("2014") {
    if let twentyFifteen = Int("2015") {
        if twentyFourteen < twentyFifteen {
            print("twentyFourteen = \(twentyFourteen)")
            print("twentyFifteen = \(twentyFifteen)")
        }
    }
}

このように書けます。この記述と最初に示したサンプルを比べれば、対応関係が分かりやすいと思います。

補足: Swift 2.xでの記述方法

Swift 2.xでは、複数のoptional bindingsや条件判定を組み合わせる場合の記述は

if let twentyFourteen = Int("2014"), twentyFifteen = Int("2015") where twentyFourteen < twentyFifteen {
  print("twentyFourteen = \(twentyFourteen)")
  print("twentyFifteen = \(twentyFifteen)")
}
//"twentyFourteen = 2014"
//"twentyFifteen = 2015"
//と表示

このようになっていました。比較すると違いが分かると思います。また、Swift 3では上記の構文だとコンパイルエラーになるので注意が必要です。

Swift 2.2から追加: 新しくなった機能という訳ではないと思いますが、上記のwhereを使ったoptional bindingsでは、条件の結合は全てandになっています。したがって、これらの条件が1つでもfalseになると、if文が実行されません。もっと具体的に言うと、optional bindingsの結果がnilになったり、where以降の条件がfalseになったり、です。

Implicitly Unwrapped Optionals

Implicitly Unwrapped Optionalsは、初期化した後に「プログラム構造上optionalsが常にある値を持つ」ということが明確な場合に使える特別なoptionalsの宣言方法です。

通常のoptionalsでは型の直後に?マークをつける(String?)のに対して、Implicitly Unwrapped Optionalsでは!マークをつけます(String!

//通常のoptionalsとImplicitly Unwrapped Optionalsの違い
let optionalString: String? = "This is optional"
let forcedUnwrapping: String = optionalString!
// 通常のoptionalsをForced UnwrappingでStringに変換

let implicitlyUnwrappedOptionalString: String! = "This is implicitly unwrapped optional"
let implicitString: String = implicitlyUnwrappedOptionalString
// Implicitly Unwrapped OptionalsをStringに変換。!は不要

このように、Implicitly Unwrapped Optionalsは値を取り出す際に!が不要です。

つまり、普通のoptionalsが自動的にForced Unwrappingの機能を持っているようなもので、使用する際にいちいち!をつけなくて良いことになります。ただし、それ以外の基本的な機能は普通のoptionalsと全く一緒ですので、使用する場合にはoptionalsを使う場合と同じ使い方ができますし、注意するポイントも一緒です。

Swiftではclassの初期化の際、Implicitly Unwrapped Optionalsが使われるようですが、使ってみないとその効果はなかなか分かりませんね。

まとめ

  • Optionalsは値があるかどうか分からない場合に使う
  • Optionalsを指定するには、型の直後に?をつける。例)String?
  • nilは「値がない」という状態を示す特別な値
  • Optionalsに値があるかどうかをチェックするには、(1)Forced Unwrapping、または(2)Optional Binding、を使う
  • Optionalsが常に値を持つと分かっている場合は、Implicitly Unwrapped Optionalsが使える
  • Implicitly Unwrapped Optionalsを指定するには、型の直後に!をつける。例)String!