[Swift] ! の使い所を考える

Swift

Swift には、Int 等の型をベースにして、Optional, Implicitly Unwrapped Optional を定義できます。使いわけを説明します

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterery beta 5
  • Xcode 13 beta5
  • iOS 15 beta
MEMO
Implicitly Unwrapped Optional は Swift5.5 以前の Swift4.2 で導入されていますので、beta を使っていることに意味はありません。
MEMO
以下のコード例では、単純化して説明するために、let と var を使い分けていません。

Int や Double

Swift には、さまざまな型が定義されています。

例えば、Int は、整数情報を保持するために定義された型です。64bit プラットフォームでは、Int64 と同じ型です。

Int についての Apple のドキュメントは、こちら
Int64 についての Apple のドキュメントは、こちら

変数を使用するには以下のように定義します。


var intVariable: Int

初期値を与える時に、その値から型が推論できるときには、型を省略することもできます。


var intVariable = 123

123 より整数と推論できるので、以下のコードと同じ意味を持ちます。


var intVariable: Int = 123
注意
あくまで、コンパイラーが推論した型が当てはめられますので、UInt 等 特定の型を使いたい時には、明示的に指定する必要があります。


var intVariable: UInt = 123

Int や Double 等の基本的な型に対して、それらを活用するための型も用意されています。

代表的なものは Array や Dictionary が挙げられます。Array や Dictionary も活用すると非常に便利に使うことができる型ですが、今回は、Optional と Implicitly Unwrapped Optional について説明します。

Optional

ケースによっては、変数に "値を持っていない" という状態を持たせたい時があります。そのような時には、変数に nil が設定されます。

例えば、以下のコードは、配列中の特定要素のインデックスを取得してくるコードです。見つからなかったときは、nil が返されます。


var students = ["Ben", "Ivy", "Jordell", "Maxime"]

// Maxime が students に含まれていると i にそのインデックスが返る
var i = students.firstIndex(of: "Maxime") 
// i = 3

// Michael が students に含まれていないので、nil
var j = students.firstIndex(of: "Michael") 
// j = nil

firstIndex(of:) についてのApple のドキュメントは、こちら

Apple のライブラリだけではなく、該当しなかった・見つからなかった等のケースで、このような返し方を多くのケースで見ることができます。

通常の変数定義では、変数型の値だけ保持でき、nil を保持することができません。nil も保持することができるような型が Swift で定義されています。
その型が、Optional 型です。

Optional のApple のドキュメントは、こちら

先の例で 型を明示的に指定すると以下のようになります。


var i: Optional<Int> = students.firstIndex(of: "Maxime")

簡単に記述する方法として、型情報の後ろに ? をつけて定義することもできます。


var i: Int? = students.firstIndex(of: "Maxime")

こうすることで、変数型の値だけではなく、nil も保持することができる変数として定義することができます。

Optional の使い所

”変数が値を持っていない” という状態を表現することが必要なケースで使います。

Array.firstIndex(of:) では、対象が見つからない時に、nil が返されていました。

Optional のデメリット

変数が値をもっていない かもしれないことを意識することが必要となります。

Optional な変数を扱う時には、"値を持っている時" "nil を持っている時" の両方を考慮することが必要です。

Optional を扱う時に便利な Optional Binding

Optional な変数が値を持っているかどうかで分岐したコードを簡単に書ける方法が用意されています。


var value: Int? = ....

if let value = value {
  // value が 値を持っている場合
} else {
  // value が nil だった場合
}
MEMO
"if let value = value" では、同名の2つの変数を使っていますが、別名にしても問題ありません。
"if let value" の value は、直後の { } 内にスコープのある変数となり、{ } 内では、if 文の2つめの value は見えなくなっています。

Variable Shadowing と呼ばれます。

Implicitly Unwrapped Optional

Optional だけでなく、Implicitly Unwrapped Optional という型も用意されています。

MEMO
型と書いていますが、じつは "Implicitly Unwrapped Optional" は、型として実装されておらず、Optional の Syntax Sugar として実装されています。

変数の型が Optional である時には、optional binding 等を使って、値を持っているかどうかをチェックしながら使うことが必要となります。

ですが、「初期化時の段階のみ nil をもつかもしれないが、初期化完了後には 確実に 値を持つ変数」 というものが存在します。

Storyboard の Button 等の UI が Binding された (ViewController 等の持つ) 変数がその代表です。

Storyboard 読み込みまでは、nil となっていますが、読み込まれれば、NSButton/UIButton が設定されているはずです。

このように、特定の段階以降では確実に値を持つ変数について、アプリのライフサイクル全体を通して、値をチェックしながら使うのはすこし面倒です。

"Implicitly Unwrapped Optional" は、このようなケースの変数に設定することができるものです。

具体的には、”常に、値を持つと想定して扱ってよい Optional 変数" というものです。

Implicitly Unwrapped Optional を日本語訳すると "暗黙的に Unwrap された optional" ですので、直訳されていることがわかります。


Implicitly Unwrapped Optional のデメリット

常に値を持つと想定して扱ってしまうため、nil が設定されている変数にアクセスすると クラッシュします。

Storyboard で Button 等の接続を忘れて実行してしまった時に クラッシュしてしまう原因の大半はこのことが理由となっています。

nil でないという保証は、アプリ開発者側にあり、コンパイラはチェックしてくれません。

Implicitly Unwrapped Optional の使い所

遅延して初期化される情報

アプリ起動時には情報として獲得/設定することは難しく、後から設定される変数が必要となる時があります。

Storyboard の Button は、代表的な例です。

特定の範囲でアプリ開発者が ”nil でないこと”を保証できる場合は、Implicitly Unwrapped Optional を使うことで、boiler plate 的な optional binding をなくすことができます。

Implicitly Unwrapped Optional の濫用は NG

適切に使うと、不要な boiler place をなくすことができて便利ですが、nil 時にアクセスすると クラッシュするということは よく考えておくべきことです。

もし、クラス設計やアーキテクチャの工夫でなくすことができる Implicitly Unwrapped Optional は、クラッシュのリスクを減らすという観点からもよく検討されるべきです。

まとめ:Implicitly Unwrapped Optional の使い所

Implicitly Unwrapped Optional の使い所
  • 遅延初期化等で、オブジェクト生成時に設定できず、その後確実に設定されるプロパティに使う

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です