はじめに
プログラミングにはカタカナ語が多すぎる!!!!!
Swiftをやってて、これらのエラーに当たったことはありませんか?
Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
Coalesce using '??' to provide a default when the optional value contains 'nil'
Force-unwrap using '!' to abort execution if the optional value contains 'nil'
で、どういう意味かと思って調べてみたものの、「オプショナル」だとか「アンラップ」だとかのカタカナ語を前にして挫けたことありません?
それで結局、よく分からずにFix
を押して、「なんでか知らんけどエラー消えたしいいや」ってなってませんか?
この記事を頑張って読めば、これらのエラーの原因がちゃんと分かるようになります。
注意書き
-
?
と!
の基礎だけを簡単に理解できるようにしたために、紹介しきれなかった概念などが少しあります。 -
この記事は、以下の内容を分かっている前提で話を進めていきます。
- 変数や型について(
Int
やString
など) -
if
文などの基礎的な文法
- 変数や型について(
-
「カタカナ禁止」を謳ってはいますが、「エラー」などの最低限のカタカナ語は使います。お許しを…。
まずは前提知識から
空っぽ(nil
)という概念
冒頭のエラーについて理解するには、まず初めにnil
という概念について理解する必要があります。
nil
というのは、変数の中が空っぽだよ、何も入ってないよということを表す概念です。
少し強引ですが、「nil
についてよう分からん」ってなったら、とりあえず頭の中でnil
を「空っぽ」と読んでください。だいたい合っているはずです。
★重要★ nil
は「空っぽ」を表す。
nil
の使いどころ
例えば、会員登録の際のデータとして、任意で年齢を知りたいという場合について考えてみます。(「任意で」というのがとても重要です。)
年齢は間違いなく整数ですから、データはInt
型の変数で持っておけば良さそうですね。
ところが、「任意で知りたい」というのが厄介です。
年齢の入力が任意ということは、「年齢の欄に何も入力されない」という場合も考えられるわけです。
そこで、年齢の欄に何も入力されていなければ、変数の中身を、「空っぽ」であることを表すnil
にしておきます。
こうすることで、「年齢が入力されなかった」ということを、nil
によって知ることができるのです。
「普通の変数」も見ておこう
実際にプログラムでnil
を使う手順について説明する前に、まずは前提知識として、皆さんがいつものように使っている「普通の変数」について考えてみます。
Swiftにおいて、「普通の変数」には必ず何かしらの「値」が入っていなければなりません。
(ここで言う「値」というのは、例えばString
型の変数では何かしらの文字列、Int
型の変数では何かしらの数字…などといった具合です。)
つまり、Swiftの「普通の変数」において、「変数の中身が空っぽ」というようなことは想定されていないのです。
そのため、Swiftでnil
を扱うには、その変数を宣言するときに、「普通の変数」を使うときとは違うことをしなければなりません。
nil
を代入できる変数を使う
変数にnil
を代入するには、型の名前の後ろに?
をつけます。
let hoge: Int = 0 //空っぽ(nil)を代入できない🙅♂️
let fuga: Int? = nil //空っぽ(nil)を代入できる🙆♂️
上の「例1」では、fuga:
の後ろのInt
にくっついている?
がそれです。
?
をつけることで、「nil
も代入できる」ということが分かりやすくなりますね。
ちなみに!
変数名になっているhoge
やfuga
というのは、「何でも良い、特別な意味のない名前」で、よくサンプルのプログラムなどで使われているものです。
このような「意味のない名前」のことを、難しい用語で「メタ構文変数」と呼びます。
★重要★ ?
をつければ、変数にnil
を代入できる。
実際にnil
を使うときの注意点
ここからは、nil
を代入できる🙆♂️ 変数を使うときの注意点について話します。
なぜ?
をつけて区別するの?
みなさん、疑問に思いませんか?
なんでわざわざ
?
をつけて普通の変数と区別するの?
普通の変数に直接nil
を代入しちゃえば良くない?↓こんな感じで↓
let hoge: Int = nil //?をつけていないのにnilを代入しているので、もちろんエラー❌
では、なぜ「例2」のように書けないのでしょう?
答えは、「nil
も代入できる」というのがなかなか厄介だからです。
nil
の厄介な一面
その厄介な点というのは、プログラムの中でnil
を直接扱うと、エラーとなり、アプリが落ちてしまうことです。
nil
を代入できる🙆♂️ 変数には、常に「もしかしたら中身がnil
かもしれない」という可能性があります。
そのような、「もしかしたら中身がnil
かもしれない変数」をプログラムでそのまま使ってしまうと、実際に中身がnil
だった場合にエラーとなり、アプリが落ちてしまいます。
使っているアプリが突然落ちるなんて、勘弁してほしいですよね。
「nil
によるエラー」を防ぐための取り組み
そのようなnil
によるエラーを防ぐために、Swiftでは
1. nil
を代入できない🙅♂️ 変数 と
2. nil
を代入できる🙆♂️ 変数
の2つを同じように扱えないようになっています。
↓具体的にはこんな感じ↓
let hoge: Int = 0 //nilを代入できない🙅♂️
let fuga: Int? = 1 //nilを代入できる🙆♂️
print(hoge + fuga) //nilを代入できない🙅♂️ と nilを代入できる🙆♂️ を同じように扱ったのでエラー❌
「例3」で、hoge
はInt
となっているので nil
を代入できない🙅♂️ 変数、fuga
はInt?
のように?
がついているので nil
を代入できる🙆♂️ 変数です。
print(hoge + fuga)
のところでは、nil
を代入できない🙅♂️ 変数と nil
を代入できる🙆♂️ 変数を同じように扱ってしまっているため、エラーになってしまっているのです。
★重要★ Int
とInt?
は同じように扱えない。
nil
にまつわる「型」の話
では、nil
を代入できない🙅♂️ 変数と nil
を代入できる🙆♂️ 変数のどこが違うために、同じように扱えないのでしょう?
実は、これら2つの変数の違いには「型」が関係しています。
(ここで言う「型」は、String
型やInt
型と言うときの「型」と同じようなものだと考えてください。)
このことについて、先ほどの「例3」の事例で説明すると、Int
となっているhoge
と、Int?
となっているfuga
が、同じように見えて実は違う「型」だということです。
ここでひとつ、考えてみてほしいんですが、下の「例4」のように、String
型の変数とInt
型の変数をまぜこぜにして扱えませんよね。
print("こんにちは" + 3) //String + Intは計算できないのでエラー❌
これと同じように、nil
を代入できない🙅♂️ 変数と nil
を代入できる🙆♂️ 変数においても、そもそも型が違うのでまぜこぜにして扱えないのです。
ちなみに!
Int?
のような「?
付きの型」には、「オプショナル型」という名前がついています。
★重要★ Int
とInt?
は違う「型」
型をそろえる / ?
を取る
つまり、「例3」のような計算をするためには、まず「Int?
をInt
に変換して、型をそろえる作業」が必要なのです。
これもう少し簡単に言うと、「?
を取る作業」 となります。
「例3」では、この作業をしなかったのでエラーになったというわけです。
★重要★ nil
を代入できる🙆♂️ 変数を使う前に、「?
を取る作業」が必要
?
の取り方3選
以下では、「?
を取る3つの代表的な方法」について、順番に見ていきます。
ひとつめ: !
一つ目は!
です。これは簡単に使えますが、力技です。
!
の使い方
nil
を代入できる🙆♂️ 変数の後ろに!
をつけます。
この!
によって、変数の中身に関わらず強制的に?
が取れます。
(「強制的に」というのがとても重要です。)
let hoge: Int = 0
let fuga: Int? = 1
print(hoge + fuga!) //fugaの"?"を取って計算している。結果は1⭕️
しかし、はじめに力技と言った通り、この方法は万能ではありません。
下の「例5」のように、fuga
の中身がnil
の場合、!
を使うとエラーとなってアプリが落ちてしまいます。
let hoge: Int = 0
let fuga: Int? = nil //例4と例5では、ここが"1"から"nil"に変わった
print(hoge + fuga!) //nilに対して"!"を使えない。よってエラーとなる❌
そのため、この方法は、変数の中身が確実にnil
ではないと分かっているときしか使えません。
ふたつめ: ??
二つ目は??
です。これも、簡単に使えます。
??
の使い方
nil
を代入できる🙆♂️ 変数の後ろに??
をつけ、さらにその後ろに、nil
だった場合に代わりに使う値を書きます。
こうすることで、変数の中身がnil
だった場合に、その代わりとして??
の後ろの値が使われます。
let hoge: Int? = 1
print(hoge ?? 0) //hogeがnilではないので、hogeの値がそのまま出力される。結果は1⭕️
let hoge: Int? = nil
print(hoge ?? 0) //hogeがnilなので、??の後ろの値が出力される。結果は0⭕️
??
を使う場合、中身がnil
だった場合の「身代わり」となる値が後ろに用意されています。
そのため、!
を使ったときのように、エラーとなってアプリが落ちることがありません。
つまり、、??
を使う方が、!
を使うよりも安全にnil
を扱うことができます。
みっつめ: if let
三つ目はif let
です。
こちらは少し難しいですが、理解さえすればnil
を扱うときの幅が大きく広がります。
if let
の使い方
新たに用意した変数に nil
を代入できる🙆♂️ 変数を入れてみて、その中身がnil
じゃない場合にのみif
文の処理を行います。
逆に、もし変数の中身がnil
だった場合、if
文の処理は行われません。
let hoge: Int? = 1
if let fuga = hoge { //とりあえずfugaにhogeを代入 → 中身がnilじゃなかったらif文の処理を行う
print(fuga)
print("nilじゃなかったよ😊")
}
//結果⭕️
//1
//nilじゃなかったたよ😊
また、else
を追加することで、変数の中身がnil
だった場合に行うべき処理も書けます。
let hoge: Int? = nil
if let fuga = hoge {
//ここは中身がnilじゃない場合に実行される (この例では、hogeの中身がnilなので実行されない)
print(fuga)
print("nilじゃなかったよ😊")
} else {
//ここは中身がnilだった場合に実行される
print("nilやんけ😡")
}
//結果⭕️
//nilやんけ😡
このように、if let
では、変数の中身がnil
でない場合にのみ行うべき処理を書くことができます。
また、変数の中身がnil
だった場合とそうでない場合で、行うべき処理を明確に分けられる点も、if let
の特徴だということができます。
ちなみに!
これまでに3つ見てきた「?
を取る作業」には、「アンラップ」という名前がついています。
まとめ
-
nil
は「空っぽ」を表す。 - 型の名前の後ろに
?
をつければ、変数にnil
を代入できる。 -
Int
とInt?
は違う「型」 -
nil
を代入できる🙆♂️ 変数を使う前に、「?
を取る作業」が必要
おわりに
この記事は、初心者の理解のとっかかりになればと考えて書きました。
そのため、「guard let
」や「オプショナルバインディング」など、説明を省いた概念や用語があります。
もし「もっと学びを深めたい」ということでしたら、下の記事を読むのが良いと思います。
この記事では説明できなかった概念や用語なども含めて、とても分かりやすく説明されています。
最後に、誤字や脱字があったり、内容が誤っていたりしたら教えていただけると嬉しいです。