SwiftのOptional型(Optional Type)に関するまとめ

スポンサーリンク

Swift の文法は、他の言語(Ruby/Python/JavaScript/Java/C++など)を知っている人であれば、おおかた覚えやすいかな〜と感じます。自分の場合で Swift の文法で特に難しいと感じたのが、表題の Optional 型についてです。

Optional 型は、Swift の言語仕様自体で nil(または言語によっては null)の扱いを安全に計らうように用意されたものだと考えていますが、ちょっと自分に馴染みのない概念だったので、学習のためにもまとめました。Optional 型を自在に操ることは、Swift プログラミングに欠かせないだろうと思います。

Swift 学習を開始してまだ1〜2週間程度ですので、間違っている箇所があるかもしれないことを前もってお詫びしておきます。誤記述がありましたらぜひご連絡ください。

【お知らせ】 英単語を画像イメージで楽に暗記できる辞書サイトを作りました。英語学習中の方は、ぜひご利用ください!
画像付き英語辞書 Imagict | 英単語をイメージで暗記
【開発記録】
英単語を画像イメージで暗記できる英語辞書サービスを作って公開しました
スポンサーリンク

何故 Swift は Optional 型を使って nil を特別扱いするのか

まず Swift では Optional 型による変数の宣言・初期化を行わない限り、変数に nil を代入することができません。変数に nil を代入できるようにするためには、Optional 型の変数として宣言・初期化を行う必要がある。非 Optional 型の変数、例えば普通の Int 型や String 型の変数には、nil を代入できない。

Optional 型を使うことで何が嬉しいかと言うと、Swift の言語仕様に則って、変数の中身やメソッドの戻り値が nil になるかもしれないことを、プログラマが明示できる点かと思います。nil の扱いを間違っている場合には、事前にコンパイラが警告を出すことができる。それにより、nil が返ることによる思わぬ実行時のエラーを防ぐ狙いがあるのでしょう。

Ruby の場合ですが、メソッド呼び出しやメソッドチェインの途中で nil が返って NoMethodError になる…というのを何度も経験したことがあります(自分のコードの書き方がまずかったせいですけど)。Rails だと Object#try という、すっきり書ける便利なメソッドが用意されています。

以下 Ruby on Rails での例です。

一番上の書き方をしていた場合で user が極稀にしか nil にならない状況だと、このバグの発生に気付きにくいです。Rails の場合でこの種のバグを防ぐには、二番目のように if によるチェックを入れるか、三番目のように Object#try を使うなどの必要がある。

ちょっと Ruby/Rails の例で話が脱線しちゃいましたが、Swift は Optional 型を使った書き方をプログラマに強制することで、上記例のような nil による問題をあらかじめ防ごうとしているのだと思います。

これらを踏まえまして、以降 Swift の Optional 型の詳細です。サンプルコードは、Xcode の playground で試しました。

Optional 型の宣言・初期化

Optional 型の変数は、以下のように「?」(クエスチョンマーク)を使って宣言する。Int の Optional 型と通常の Int 型、String の Optional 型と通常の String 型の例。

以下 Xcode の playground で、それぞれ Optional 型と Int 型の変数に nil を代入してみた例です。

まず Optional 型の変数の初期値は nil になっていることが分かる。一方 Int 型の変数には何も入っていません。また、Optional 型の変数には nil を代入できるが、非 Optional 型の変数には nil を代入できないのを確認できます。

Optional 型の宣言と初期化を同時に行うこともできる。

Int?, String? は Optional<T> の省略形

Optional 型を宣言時の T? という書き方は、Optional<T> の省略形(シンタックスシュガー)となっています。なので、以下のように書いても同じです。

通常は、Int?, String? の書き方のほうが短くて簡単なので、こちらを使うようです。Optional 型( Optional<T> )は、nil または何かしらの型( T型 )の値を格納する型であり、enum 型として定義されているそうです。以下参照。

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

つまり、Int? は nil または Int 型の値を格納できる型、String? は nil または String 型の値を格納できる型となる。Int? と Int は別の型であり、String? と String も別の型であります。

Optional 型( Optional<T> )は nil を代入できる型と考えるよりも、「nil またはT型の値を格納できる型」と考えたほうが理解しやすいと思います。

Optional<T> をT型の変数として扱うには unwrap(アンラップ)する

次に Optional 型の変数に対してメソッドを呼び出す方法です。まずは playground で実験。

Optional<String> 型の変数 optionalStr に対して、通常の String 型と同じように、uppercaseString メソッドを呼び出そうとしたところエラーになりました。これは、Optional<String> 型と String 型は全く別の型であり、Optional<String> 型には uppercaseString メソッドがないため。

Optional<String> 型の変数 optionalStr を、通常の String 型として扱うために必要となるのが unwrap(アンラップ)という仕組みです。Optional 型( Optional<T> )の変数をT型の変数として扱うには、unwrap する必要があります。

Optional 型の変数のことをラップ(wrap)されているという表現するそうなのですが、これはT型が Optional 型に含まれるという意味合いかと思います。アンラップ(unwrap)という表現は、Optional 型のラップを剥がして(アンラップして)、T型に戻すという意味だろうと捉えています。

unwrap する方法3つ + 1つ

Optional<T> 型である変数を unwrap すると、T型の変数として扱えるようになる。unwrap する方法は、基本的に以下の3つに分類されます(他にも方法はあるみたいですが、とりあえずここでは基本のみ)。

・Forced Unwrapping
・Optional Binding
・Optional Chaining

他に Optional 型の仲間のような型に、ImplicitlyUnwrappedOptional 型( ImplicitlyUnwrappedOptional<T> )があり、これについては別途説明。

・Implicitly Unwrapped Optional

Forced Unwrapping

Optional<T> 型の変数の後ろに「!」(エクスクラメーションマーク)を付けると、強制的に unwrap して変数をT型として扱えるようになります。以下の例では、optionalStr! と「!」を付けて uppercaseString メソッドを呼び出しています。

ただし、Optional 型の変数が nil である場合、ランタイムエラーが起こる(アプリの場合だったら落ちる)可能性があるので、Forced Unwrapping をそのまま使うのは良くない。変数の中身が nil ではないことが明らかな場合や、nil チェックをした後に Forced Unwrapping するようにします。

nil チェックした後 Forced Unwrapping する例。

Optional Binding

Optional Binding とは、Optional 型の変数の中身が nil でないことを確認すると同時に unwrap を行うための、if を使った構文のことです。以下のように書く。

if 文で optionalStr が nil でないか確認された後、nil じゃない場合は unwrap して変数 unwrappedStr に値が代入されます。Optional Binding の if ブロックの中では、unwrappedStr をそのまま使える。

この例では、Optional Binding の if 文で let(定数の宣言)を使っていますが、var(変数の宣言)を使うこともできます。

Optional Chaining

Optional Chaining は、一旦 unwrap した変数の型のメソッドを使用したい場合に使う方法です。以下のように「?」(クエスチョンマーク)を、Optinal 型の変数の後ろに付けて、メソッドを呼び出します。

optionalStr が nil である場合は、3行目 optionalStr?.uppercaseString の結果は nil が返されます。つまり、Optional Chaining で返される値の型は、nil を許容できる Optional 型となります。

変数が nil の場合は何もせずに nil を返し、nil じゃない場合は何かしらの処理を行った値を返す、という一連の処理を簡潔に書くことができる…のですが、返される値は Optional 型であることに注意が必要です。unwrap された値を得たい場合は、Forced Unwrapping や Optional Binding を使う必要があります。

また注意点として、Optional 型の変数の宣言・初期化で使う「?」と Optional Chaining で使う「?」は全く別物であること。ここ最初のうち混乱したので、Swift 入門な方は注意しておくと良いかと思います。

Implicitly Unwrapped Optional

nil と任意の型の値を格納できる型の変数の宣言・初期化の方法として、「!」(エクスクラメーションマーク)で表現する ImplicitlyUnwrappedOptional 型を使う書き方もあります。以下の通り。

ImplicitlyUnwrappedOptional 型の変数 implicitlyStr は、メソッド呼び出し(ここでは uppercaseString メソッド)の際に、通常の String 型のようにメソッドを呼び出すことができます。

ただし、Forced Unwrapping 同様に、ImplicitlyUnwrappedOptional 型の変数が nil の場合、ランタイムエラーとなる可能性があります。nil でないことが確実な場合や、if 文での nil チェック、Optional Binding などを使って処理するほうが良い。

なお、T! は ImplicitlyUnwrappedOptional<T> の省略形(シンタックスシュガー)です。また、Forced Unwrapping で使う「!」と ImplicitlyUnwrappedOptional 型の宣言で使う「!」は別物であることに注意。

Int?( Optional<Int> )型の値の計算

具体例として、Int? 型の値の足し算を行う例です。Int? は、nil または Int 型の値を格納できる Optional 型ですので、計算を行う際には unwrap が必要となります。

Forced Unwrapping

Optional Binding

上記のように書けます。

Int? 型の変数が nil でない時は足し算を行い、nil の時は nil を返す

よくあるパターンで Int? 型の変数 num が nil でない時は足し算を行い、num が nil の時は nil を得るような処理。Optional Binding を使っています。

とても冗長な書き方になってしまいます…。計算した結果の値または nil を返す、というよく使うこのパターンを、安全で簡潔に書けないものかと調べていて見つけたのが以下の記事です。

SwiftのOptional型を極める – Qiita

Optional 型について一歩進んで解説してあります。個人的には大変役に立ちまして、ありがとうございました。

上記ページに詳細が書いてあるのですが、Optional 型には map という便利なメソッドが用意されている。この map メソッドを使うと、計算した結果の値または nil を返す(結果を Optional 型で返す)処理を、安全かつ簡潔に書けます。

num が nil の時

num が nil ではない時

このように、Optional の map メソッドを使うと安全かつ簡潔に処理を書くことができます。$0 とか出てきてちょっと暗号めいていますが、$0 はクロージャのブロックの引数名を省略する場合の書き方です。

この辺りの詳細についても、SwiftのOptional型を極める – Qiita に解説があります。さらに発展した内容についても分かりやす解説されてありますので、気になられる方はぜひご参照ください。

以上ですが、自分なりに Swift の Optional 型を学習してのまとめでした。

スポンサーリンク
 
スポンサーリンク

Leave Your Message!