【Swift】オプショナル型とは?4種類のアンラップ方法と共に解説!

Programming

Swiftにはオプショナル型というものが存在し、このおかげで言語としてnull安全が保たれています。
オプショナル型の概念を知ること自体はそこまで難しくはありませんが、オプショナルを解除するアンラップと呼ばれるものにやり方がいくつかあって頭の整理が大変です。

そこで今回の記事では、オプショナル型の概念と2種類のオプショナル型について軽く触れた後、アンラップ方法を大きく4パターンに分けて整理してみます。

オプショナル型と非オプショナル型

オプショナル型というのはnil(他言語におけるnullみたいなもの)を格納することができる型です。勘違いのないようにいっておくと、「nilだけを」代入できる型ではなく「nil」代入できる型です。
逆に非オプショナル型はnilを代入することはできません。

Swiftに存在する変数・定数の型は、まずオプショナル型(Optional)か非オプショナル型(non-Optional)かに大別できます。
その中で、オプショナルのString型、オプショナルのInt型、非オプショナルのString型、非オプショナルのArray型などが細かく分かれていくイメージです。

つまり、オプショナルのStringと非オプショナルのStringは全く違う型です。サブクラスとかとは訳が違うので注意してください。全くの別物です。

ここまででわかるように、オプショナルのString型やオプショナルのInt型は存在すれど、「オプショナル型」などというものは実体として存在しないことがわかるかと思います。
オプショナルのString型は「オプショナル型とString型で2つの型を持っている」みたいなイメージに捉えられがちですが、オプショナルのString型はオプショナルのString型という1つの型です。
同様に「非オプショナル型」なんてものもありません。あるのは「非オプショナルの〜〜型」です。

また、「String型」というのもありません。
var str: String
は「String型」ではなく「非オプショナルのString型」です。

ところでなぜこんな勘違いがされやすいのかといえば、例えばnon-OptionalのString型を宣言する時の表記方法が、他の言語における「普通のString型」を表記する形と似ているので、それが「non-OptionalのString型」だと明確に認識しにくいからかだと思われます。
OptionalのString型表記が
var str: String?
と?がつくので分かりやすいのに対して、(ちなみにString?Optional<String>のシンタックスシュガーです。)
non-OptionalのString型表記は
var str: String
だけなので、あたかもこれが「只のString型」だとイメージしてしまいます。

ここまでで、変数・定数の型は必ず「オプショナルの〜〜型」か「非オプショナルの〜〜型」であるので、本来var str:String は「非オプショナルのString型」と表記すべきであることがわかったかと思います。
しかしいちいち「非オプショナルの〜〜型」なんて表記するのは面倒臭いので、「非オプショナルの〜〜型」のことを単に「〜〜型」と表記することがほとんどです。

この記事でもここからは普通に「〜〜型」と書いた時、それは暗に「非オプショナルの〜〜型」を指していることにします。

オプショナル型のアンラップ

var str1:String?

var str2:String
は「オプショナルのString型」と「String型」で全く違う型だということが分かりました。
そのため、「オプショナルのString型」であるstr1では、String型で使えるuppercased()メソッドやcountプロパティなどにアクセスできません。

しかし、str1はオプショナルのString型で宣言してしまったが、入れた文字列の文字数をカウントしたいのでやっぱりString型に直したい!なんて時が出てくるかと思います。
ここでオプショナルの〜〜型を非オプショナルの〜〜型に変換することをアンラップ(Unwrap)と呼びます。

例えば「オプショナルのString型」と「String型」のイメージとして、オプショナルというラップをString型に被せると「オプショナルのString型」になる、みたいなイメージがよく使われます。
このイメージでいうと、「オプショナルのString型」はオプショナルというラップを剥がせば「String型」になります。
アンラップのイメージはこんな感じです。

オプショナル型と暗黙的アンラップオプショナル型

オプショナル型はさらに2つに分類できます。
通常のオプショナル型(Optional)と、暗黙的アンラップオプショナル型(Implicity Unwrapped Optional)です。

通常のオプショナル型は型名の後に?をつけて宣言します。非オプショナル型にアンラップするには、下で紹介するような明示的なアンラップが必要です。

暗黙的アンラップオプショナル型は型名の後に!をつけて宣言します。その後その変数・定数に参照する時には、非オプショナル型に強制的にアンラップされます。明示的なアンラップ処理は必要ありません。
その分、もし中身がnilだった時に強制的にアンラップしてしまうとエラーになってしまいます。

ここまででオプショナル・暗黙的アンラップオプショナル・非オプショナルの違いを表にまとめるとこうなります。

宣言時 実体 参照時のアンラップ
オプショナル ?をつける Optional 必要
暗黙的アンラップオプショナル !をつける Optional 不要(自動でforced Unwrapされる)
非オプショナル 何もつけない non-Optional 不要(元々non-Optional)

アンラップのやり方4パターン

オプショナル型の場合、暗黙的アンラップオプショナル型と違って明示的にアンラップをする必要があります。
アンラップのやり方は大きく分けて4種類あります。

ForcedUnwrapping(強制アンラップ)

str!
のように変数名の後に!をつけると、強制的にOptionalが剥がされアンラップされます
そのためnilが入ったままアンラップしてしまう可能性があり、アプリのクラッシュが起こりえます。


let hoge:String? /*Optional型のhogeにnilが代入される(Swiftでは宣言時に何も代入しないと
自動的にnilが代入される)
*/
print(hoge!)  //hogeにnilが入ったままString型にアンラップするのでエラー

OptionalBinding

実際の開発では一番よく見る形で、nilチェック(変数・定数にnilが入っていないかどうかをチェック)とアンラップを同時に行うやり方です。3パターンほどやり方があります。

if let構文

ifブロックの中でアンラップされた変数・定数を使っていきます。現場ではよく、アンラップ前の変数とアンラップ後の変数を同じ名前にするシャドーイングというテクニックが使われます。
ifブロックを抜けるとアンラップされた変数のメモリ区画は解放されてしまうので、アンラップされた変数を使いつつ続けて他のアンラップ処理も行いたい場合は、ネストがどんどん深くなってしまいます。


let hoge:String? = "Hello"

if let hoge = hoge {
//オプショナルバインディング時に限り、代入する側と同名の変数を宣言することができる これをシャドーイングという
print(hoge)
/*hogeにはif let hoge = hogeでの右辺がアンラップされた値が代入されている
また、シャドーイングの効果によりアンラップ前の(代入された側の)メンバ変数hogeにはアクセスできない
*/
}

print(hoge) /*シャドーイング影響下にあるif letブロックを抜けたのでメンバ変数hoge:String?にアクセスできる*/

if var構文

if let構文の時のhoge:Stringが、var宣言なので再代入可能になるだけです。あまり使わないかと思います。

guard let構文

guard文を使うとブロック外までシャドーイングが継続する上、早期リターンで深いネストを生じさせないので一般的には可読性の高いコードになります。


let hoge:String? = "Hello"

guard let hoge = hoge else { throw NSError() } //なんらかの関数内ならreturnで抜けるだけでもよし
print(hoge)
/* guard letの場合シャドーイングの効果がブロック外まで継続するので、
もはや元のメンバ変数hoge:String?にはアクセスできない
*/

Nil Coalescing

??演算子を使って、オプショナル型の変数の後に、もしnilが入っていた時に代わりに使うリテラルを記述します。


let hoge:String? = "Hello"
let str = hoge ?? ""
//hogeがnilでないなら、String型にアンラップされた値が、nilなら??の後の空文字""がstrに代入される
print(type(of: str)) //Stringと出力
print(type(of: hoge ?? "")) //もちろんこれでもOK Stringと出力

OptionalChaining

オプショナルの〜〜型に対して、非オプショナルの〜〜型のメソッドやプロパティにアクセスしようとした時、?をつけてからアクセスします。
nilが入っていたらその参照を取りやめ、値がちゃんと入っていたらアンラップしてアクセスしてくれます。
対象のオプショナル自体がアンラップされて取り出されるわけではないので、他のパターンと組み合わせて使うことが多いです。


let hoge:String? = "Hello"

print(hoge?.uppercased())
// hogeがnilなら何もしない この場合はhogeに値が入っているので実行される

しかしアンラップされるのはあくまでアクセス時の一瞬のみなので、返り値は

Optional("HELLO")

とOptional型のままになってしまいます。
よって返り値をnon-Optionalで使用したいときは

print(hoge?.uppercased() ?? "")

guard let uppercased = hoge?.uppercased() else { return }
print(uppercased)

のようにして、他のアンラップ方法と組み合わせます。

参考リンク

【Swift】Optional型を安全にunwrapしよう - Qiita
## これはなに Swift の型は nil を許容しないので、許容するために Optional を使う場面があるでしょう。 Optional を使って、代入や参照の際に nil にアクセスしてランタイムエラーで落ちるのはもうやめま...

コメント

タイトルとURLをコピーしました