【Swift入門】オプショナル型を理解しよう

swiftの大きな特徴といえばOptional型ですね。
ですが初心者にとっては、Optional型というと、

  • Optionalってnilが入るやつ
  • ?と!を使う
  • !を使ったときにnilが入るとアプリがクラッシュしてやばい
  • Xcodeに言われるがままに「!」を多用する

こんな感じなふわふわした認識の人が多いかと思います。
今回そんな人のために、ふわふわした認識からちゃんと理解して安全に取り扱えるレベルまでなってもらえるようにこの記事を書いていこうと思います!

Optional型とは

Optional型とは通常の値とは別に「扱うデータがない」という状態をとれる型です。他の言語ではString型やInt型といった通常の型にNULLという値が入ると思いますがSwiftでは通常の型にはNULLを入れることができず、もしNULLを扱いたい場合はOptional型という型を使わないといけません。Optional型がない言語では、プログラマーが想定していないところでNULLが入った場合に予期せぬ挙動が気づかないところで起こってたりする場合もありますが、Optional型があることで文法的にエラーを返してくれるので、文法通りに書けばバグをある程度回避できるメリットがあります。

といってもイメージがわかないと思うのでまとめると、

  • Optional型にするとnilが使えるようになる。
  • Optional型は「?」や「!」を使って表現する。

Optional型の使い方

初心者の人にとってはOptional型は「?」や「!」を使うぐらいしかわかってない人が多いと思います。というのも使い所によって役割が違うからです。なので役割ごとに分けて説明します。

説明するにあたってまず分けて考えないといけないのが、「!」に関してはOptional型以外にも使われる部分があるので先に整理しておきましょう

真偽値として使われる「!」

Optional型以外に「!」が用いられるのが真偽値です。if文とかで使われるやつですね。

これとか

主にこの2種類があり、両方とも否定を表します。

  • 「!=」
  • 定数名や変数名の先に「!」がつく

これらの場合は、Optional型での「!」ではなく真偽値としての「!」というのに注意しましょう。

以上を踏まえた上でOptional型の「!」「?」の使い所の分類をしていきます。大きく分けて2つあります。

  • 型宣言で使う
  • それ以外のところで使う

この2種類になります。

型宣言で使う

Swiftでは変数や定数を定義する際に型も宣言しなければいけません。

というように型を定義しますが、上の例のcみたいに代入する値から型が明らかな場合は型推論という機能がありswiftが型を推測してcはString型と認識してくれます。本来は値から方が明らかな場合は宣言時に型を書かないほうがいいというセオリーがありますが今回はわかりやすいように毎回型を宣言するようにします。

Int型やString型というように様々な型がありますが全ての型に「?」をつけることでOptional型にすることができます。
例えばInt?とすればオプショナルInt型と呼び、Int型の値とnilを代入することができます。

まとめ

型宣言時に (型名)? とすることによって、オプショナル(型名)型となってその型の値に加えてnilも代入できるようになる。

そういえば「!」使ってなくない?って思ってる人もいるかもしれませんが、Int!というように書いた場合に関してもオプショナルInt型になりますが、特殊な書き方になるのでまたあとで説明します。

それ以外で使う

型宣言で使う場合は自分で定義するのでわりとわかりやすいですが、それ以外の場合で使うのは主に計算する場合です。

まず以下の例を見てみましょう。

このようにエラーを起こしてしまいます。これはどうしてでしょうか?

aとbをprintしてみたところbのほうはOptionalというものがついています。
Optional型では本来の値をOptionalのラップが包んでいて本来の値としての計算ができません。
本来の型の値に加えてnilを代入することができるようになる代わりに、そのままでは計算などに使うことができなくなってしまいます。

もうひとつ例を用意しました。

aはString型の配列でこのようにアクセスすることができます。
それではこの場合どうでしょうか?

bはString型の配列のOptional型です。aのときはa[0]というようにすれば配列の中の要素にアクセスすることができましたが、この場合エラーになってしまいます。これもOptionalのラップが配列全体を包んでいるので中身にアクセスすることができません。

おまけにですがこの場合はどうでしょう?

cはオプショナルString型の配列で、配列全体が包まれているわけではなく、配列の要素1つ1つがラップに包まれているだけなのでc[0]というようにしてもアクセスができます。

なんとなくイメージはわいてきましたか?

ここまでの説明だと、まだOptional型の値を使うことができません。
では値を使えるようにするにはどうすればいいでしょうか。それは包まれてるラップを剥がしてあげればいいのです。

アンラップ

Optionalのラップを剥がすことをアンラップするといいます。
ラップをない状態にするのでunラップですね。

まず最初に一番簡単なアンラップの方法です。
ここでひさしぶりに「!」がでてきます。

aの後に「!」をつけることによってOptionalのラップがはずれています。
このように変数名、定数名の最後に「!」をつけることでアンラップすることができます。
しかしこの方法には致命的なデメリットがあって、

このようにnilがはいったものを「!」を使ってアンラップするとアプリがクラッシュします。
本来この方法はその変数にどんな値が入っていようが強制的にアンラップするので「Forced Unwrap」といいます。その変数にnilが入っていないという保証がある場合に使います。
一番簡単な方法とか言って紹介しておきながら、僕らの開発チームではこのアンラップの方法は特別な場合を除いて絶対に使わない決まりになっています。思わぬところでnilが入ってしまった瞬間にアプリがクラッシュしてしまうからです。

とはいえ、nilが入らないようにいいかんじに分岐すればいいんじゃね?ってプログラミングしたことがある人なら最初に思いつくと思うのがif文で分岐させる方法です。

というようにif文で分岐させれれば、アンラップ処理ができるようになるので良さそうに見えますが、こういった分岐が複雑になっていったときに思わぬところでnilが入ってしまいアプリがクラッシュする要因になります。もう一度いいますがForced Unwrapは危険です。
ということでこれと似た構文でアンラップ処理ができるものがswiftでは用意されています。それが if let構文です。

if let 構文

上の例で言うと

すこしスッキリ書くことができ、「!」をつかわずに処理をすることができます。
ポイントとしては上のcを例に取ってみると

  • cがnilじゃないとき、アンラップした値がdに入りif文の中に入る
  • dはif文の中で使える
  • cがnilの場合else文の中に入る
  • aの例のようにelse文を省略もできる

となります。こうすることで安全にアンラップ処理を行うことができます。
普通にコードを書く文には if let構文がわかればなんとかなりますが、次のようなデメリットもあります。

  • アンラップ処理はif分の中にしかかけないので絶対にネストする
  • 複数のアンラップ処理があった場合にさらにネストが深くなる
  • if文の外に出るとアンラップしたものは使えない
    これらようなデメリットが有り、見にくいコードになりやすくなってしまいます。

それを改善するのが guard let構文です。

guard let 構文

このようにさらにスッキリ書くことができます。

if let 構文に比べて

  • ネストが深くならない
  • アンラップしたものをずっと使うことが出来る

といったメリットがありますが、

  • nilの場合は処理を終了させてしまう前提なのでそこで処理が終わってしまいます(書き方次第ではありますが)

といったデメリットもありますが、これに関しては使い方次第です。nilの場合は何もしない場合とかはとくに便利です。

ここで少し話を戻すと、アンラップ処理をif文から順を追って説明しましたが、if文を使う際に三項演算子を用いてスッキリ書くテクニックがありますね。それを今回のアンラップの話に置き換えてみると、

このように書くことが出来ると思います。
おさらいですがここででてくる「!=」は否定の意味ですよ!
三項演算子でbには a != nil がtrueだった場合に aを代入し、falseだった場合に、0を代入します。
スッキリ書くことが出来るようになりましたがこれも「!」が使われててクラッシュする危険性を残しています。

これもswiftでは構文が用意されています。

nil合体演算子

上の例で言うと

このように、
(nilじゃないときにアンラップして代入する値) ?? (nilのときに代入する値)
というように分けます。
こうすることで、スッキリ書くことができ、「!」も使わないで書くことができます。

僕らのチームではif let構文は殆ど使わずに、

  • nilのときに処理を終わらせたいときにguard let
  • nilのときでも何かしらのそれっぽい値を入れて処理を続けたいときはnil合体演算子

この2つを使い分けています。

ここまで簡単な値を例に取ってきましたが、途中ででてきて放置してたこの例をもっかい引っ張り出してきましょう。

これは配列全体がOptionalのラップに包まれているので配列の要素にアクセスできないという例でした。
上で書いたアンラップの方法を使ってアクセスできる配列の中身を取り出せるようにしていきたいと思います。

このようにすることで配列の要素にアクセスすることができました。
ここで次の例を考えてみましょう

swiftでは配列に対してcountというメソッドが用意されていて、配列の要素の数を返してくれます。しかしbの場合は配列全体がOptionalのラップに包まれているのでcountというメソッドを使うことができません。
おそらくこのコードを書いたときにXcodeに

let countB = b!.count

と書きなさいと言われると思います。初心者の人だとよくわからずそれに従ってしまうわけですね。
Optional型の配列だったbが「!」をつけることによってアンラップされて普通の配列になるのでcountが使えるようになりますが、もしbがnilだった場合にアプリがクラッシュするので危険です。
これに対しても先程配列の要素にアクセスするようにしたように、

というようにすればちゃんと適切な値が返るようになりました。このままでも十分な気がしますが、これもさらに簡単に書くことができます。

Optional Chaining

Optional型の場合に上の例みたいにメソッドを使ったり配列内の要素にアクセスしたいときはアンラップしてから使うことになり毎回アンラップ作業が必要になります。これを解決するのがOptional Chainingです。
先程の、

let countB = b!.count

ではbがnilの場合にクラッシュしてしまいますが、これを以下のように書くことができます。

こうすることでguard letをはさむことなくcountを使うことができました。しかしよく見てみるとcountBはOptinal型になっています。
これはなぜかというとbがnilではない場合にはbをアンラップしてから配列の要素を数えてくれて4が返ってきますが、bがnilの場合はcountは計算せずにnilを返してくれます。bの値によってcountBに入る値がnilも含んでいるのでcountBはOptional型になります。
countBがOptional型にしたくない場合はどうすればよいでしょうか?

先程のnil合体演算子を使えばbがnilの場合に0が入ってくれるのでcountBはOptional型ではなくなります。今の話は、型推論の知識も少し必要になるのでもしわからなければ型推論を学ぶと良いと思います。
このコードだと?がいっぱい出てきてて、しかも全て役割が違いますが、ここまで読んでいればそれぞれの意味が全てわかるようになったかと思います。

これだけだとOptional Chainingのメリットがあまり感じられないかもしれませんが、軽く例をあげてみると、

このように2つ「?」が続いた場合にif let や guard let でアンラップした場合に処理が多くなってしまいますが、この書き方で書いた場合はselfもtableViewもnilじゃないときに全体が実行されます。逆に言うとどちらかがnilの場合はなにも起こりません。今回は2つの場合でしたがこれが何個でも続く場合もこのように書くことができます。

  • 何個でも?で続けることが出来る
  • どこでnilになってもないも起きないだけなので安全

このようなメリットがあります。

さて、だいぶ長くなってしまいましたが以上でオプショナルの説明は終わりです。最後にまとめると

? !
型宣言 Int? Int!(後日書きます)
アンラップ nil合体演算子 ?? Forced Unwrap(使わない)
Optional Chaining b?.count b!.count (使わない)

こんなかんじでした。

SNSでもご購読できます。

コメント

コメントを残す

*