オプショナル型

投稿者: | 2016年11月17日

これもSwift3になって変わった点のひとつですが、DateComponentsのプロパティをprint()で表示する際にOptionalという表示が出るようになりました。

$ cat test.swift
#! /usr/bin/swift
import Foundation
let cal: NSCalendar = NSCalendar(identifier: NSCalendar.Identifier.gregorian)!
let components: DateComponents = cal.components([.year], from:Date())
print(components.year)
$ swift test.swift
test.swift:5:7: warning: expression implicitly coerced from 'Int?' to Any
print(components.year)
      ^~~~~~~~~~~~~~~
test.swift:5:18: note: provide a default value to avoid this warning
print(components.year)
      ~~~~~~~~~~~^~~~
                      ?? <#default value#>
test.swift:5:18: note: force-unwrap the value to avoid this warning
print(components.year)
      ~~~~~~~~~~~^~~~
                     !
test.swift:5:18: note: explicitly cast to Any with 'as Any' to silence this warning
print(components.year)
      ~~~~~~~~~~~^~~~
                      as Any
Optional(2016)

正確にはNSDateComponentsがDateComponentsに変わった際にプロパティの型が非オプショナル型からオプショナル型に変わったというのが正しい内容です。…オプショナル型の意味がわからないと何のことやら???ですね。

オプショナル型については数値をチェックするの記事でそんなもんと思って使うとしてさらっと触れていますが、ここで少し掘り下げておきましょう。(あくまで私の理解の範囲内で)

型のおさらい

データの型については自明として特にこれまで解説してきませんでしたが、一般的にはintとかcharとかstringとかあります。整数なのか文字なのか文字列なのか、などデータの種類を指定するものですね。

乱暴にまとめておくと、値はすべて01ビットの組み合わせで表現されてしまうので、型を指定しないと対象の値が何ビット分なのかだとか、そのまま数値として扱うのか文字コードとして扱うのかだとかがわからなくなるので、定数や変数を宣言する際に型指定をするということになります。

オプショナル型って?

データの型指定の際、必ずしも初期値を設定するとは限らないため変数が定義されても実際の値が代入されていない状態があり得ます。この値がない状態をnilという記号で表すわけですが、この状態を許可するのがオプショナル型。nilの状態を許さないのが非オプショナル型になります。宣言時の型指定を表にまとめます。

オプショナル非オプショナル
整数int?int
文字char?char
文字列string?string

オプショナル型には値を与えてもOptional(値)という状態になっていて、これをprint()で表示するとそのまま”Optional(値)”と表示されてしまい、ハロワの動作を時間ごとに変える/野望編数値をチェックするにあるように実行時には変数名に”!”を付けないと意図通りに表示されないということになります。

なぜこんなややこしいものがあるかと言うと、nil状態のデータを計算したり表示したりするとエラーでアプリケーションがクラッシュするからだそうです。

オプショナル型の扱い方

先ほど説明したとおり、オプショナル型の変数を計算したり表示したりする場合には”!”を付けるとうまく行きますが、この方法は注意して使わなくてはいけません。

なぜなら”!”を付けた途端、無条件に非オプショナル状態になってしまうから。もしもnilの値を持つ状態で表示されたり計算されたりすると、やっぱりエラーになってアプリケーションがクラッシュします。したがって”!”を付けて使用する場合には必ずnilでないことを確認したうえで使うようにします。

ちなみにオプショナル状態の値を非オプショナル状態に変換することをアンラップといい、このように無条件にアンラップすることを強制的アンラップと言います。

nilでないことを確認する

というわけでnilでないことを確認したうえで安全にアンラップする方法です。この方法はオプショナルバインディングと呼ばれます。

var optional: String?
// ~何らかの処理~
if let non_optional: String = optional {
  print(non_optional)
}
else {
  print("optionalはnilです")
}

1行目で宣言している変数optionalは初期値を与えていないのでオプショナル型で宣言しないとエラーになる。3行目で宣言している定数non_optionalは初期値としてoptionalの内容を与えているので非オプショナル型宣言ができる。

このとき2行目で省略している何らかの処理の中でoptionalに値が与えられていれば、3行目の非オプショナル定数non_optionalへの値の代入は問題なくおこなわれifの条件を満たすため、4行目でoptionalに与えた値をコピーしたnon_optionalの内容をprint()で表示することになる。このときnon_optional自体は非オプショナル型なのでダイレクトに値が表示される。(Optionalなど余計な表示が加わらない)

仮に2行目で省略した何らかの処理の中でoptionalに値が与えられずnilであった場合、3行目の非オプショナル定数non_optionalにnilを代入することになり処理に問題がある状態となるため、if文は条件を満たさずelse節が実行されることになる。(この場合「optionalはnilです」と表示される)

結果optionalに与えられた値は非オプショナルに変換されてprint()表示されるか、nilであった場合にはエラーでクラッシュすることなく無事にエラー処理をおこなうことができる。

まとめ

本当はもう少しいろいろあるのですが、混乱しそうなので一旦この辺で切ります。

  • 値の存在しない状態nilというものがある
  • 初期値を与えないまたは途中でnilを代入される可能性のある変数はオプショナル型で宣言する
  • オプショナル型の宣言はデータ型の後ろに”?”を付ける
  • オプショナル型の値を表示したり計算するときは変数名の後ろに”!”をつけると非オプショナル状態に変換できる
  • 非オプショナル状態に変換することをアンラップという(Optional()に包まれている値を取り出す)
  • 無条件にアンラップする強制アンラップは危険なのでオプショナルバインディングで値を取り出すほうがいい
Optionalの効能
nilによるアプリケーションクラッシュを防ぐ仕組

こんな感じです。

コメントを残す

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