オプショナルバインディングについての細かな確認もひと通り終わり、今回こそは幾度と予告しつつも先送りになっていた The Basics
における オプショナル
の最後の項、自動で強制アンラップされるオプショナル
から眺めていけそうです。長らく Swift に親しんでいる人は、かつては "オプショナル型とは別の型" として存在していたその存在を思い出しつつ眺めてみると良いことあるかもしれません。Swift に触れ始めて間もない人にも、癖がありつつときどき使う場面のある機能なので、この機に 自動で強制アンラップされるオプショナル
の基本を確認しておきましょう。どうぞよろしくお願いしますね。
今回もゆめみ社外に向けた参加者公募がされていて、ゆめみの外の人も幾名か来られての開催になります。
——————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #172
00:00 開始 00:10 Implicitly Unwrapped Optional 02:29 オプショナルのおさらい 02:41 定数におけるオプショナル表現 04:04 定数におけるオプショナル型の初期値 08:30 定数と確定初期化 10:34 変数で扱うオプショナル型のときだけ特殊と捉える 11:28 変数と定数でのオプショナルの初期化の挙動を比べてみる 12:08 オプショナルな定数って使う? 13:21 オプショナルと非オプショナルは等価比較可能 15:01 オプショナルな定数が必要そうなとき 15:39 テスト時の既定値の差し替えで使える可能性は? 17:30 メンバー定数にしてイニシャライザーで初期化する案 18:15 使いどころはあるかもしれない 19:01 定数を特別扱いしていないだけの可能性 20:10 あまり出番がなさそうな理由 22:46 異なるサイズの整数同士で比較可能 24:41 使うまでには値を用意できるときのオプショナル 26:44 暗黙アンラップのオプショナルを使う 27:03 感嘆符を使うときではなく宣言時に記載 28:30 IUO 属性つきのオプショナル型 29:55 ImplicitlyUnwrappedOptional 型 31:23 クロージング 31:51 Optional 型の表記について ———————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #172
さて、始めていきますね。今日はオプショナルに関して、特に「暗黙的にアンラップされるオプショナル」についてお話しします。このテーマは何度も告知しつつ、なかなか辿り着けなかった内容です。
「暗黙的にアンラップされるオプショナル」ですが、この日本語訳も少し無理やり感があります。英語では「implicitly unwrapped optional」と言い、一般的には「IUO(Implicitly Unwrapped Optional)」と略称されます。統尾アンラップのオプショナル、これを指して IUO と呼び習わす人も多いです。
IUOの例として、例えば以下のようなコードがあります:
@IBOutlet var titleLabel: UILabel!
ここで UILabel!
と書かれていますが、これが要するに「暗黙的にアンラップされるオプショナル」であり、強制アンラップのオプショナルとも言います。この !
で終わる宣言は、「nil かもしれないが、アクセスするときには値が存在することを前提として扱う」ものです。
さて、本題に入る前にスライドの内容を確認してみましょう。暗黙的にアンラップされるオプショナルを理解するための前提が書かれています。これまでに触れてきたオプショナルのおさらいから始めます。
オプショナルは、定数や変数が値を持たない可能性を示すために使用されます。値が nil
であっても良いことを示すために、変数や定数に対してオプショナルを使いますが、定数に対してはオプショナルを使うことはあまりありません。
オプショナルは値があるかどうかを if
文で検査することができ、値がある場合にはアンラップ(オプショナルバインディングとも言います)してアクセスできます。いろいろな方法でアクセスできる方法を紹介してきましたが、要するに「何か値があればその値を使う」というのがオプショナルの大前提です。
ここまでがオプショナルのおさらいです。次に、強制アンラップオプショナルの話に進む前に少し補足しますね。
定数や変数におけるオプショナルの初期値には違いがあります。具体的には、以下のコードを見てください。
var value: Int? // 初期化をしない
print(value) // これは 'nil' を出力します
これは変数 value
が初期化されていないので nil
となります。一方、これが定数だったらどうなるでしょう?以下のコードを見てください。
let value: Int?
print(value) // これはコンパイルエラーになります
変数の場合は初期化されていなくても nil
とみなされますが、定数の場合は nil
であるとみなされず、初期値が必要ですが設定されていないためコンパイルエラーになります。同じ動きをさせたい場合は、直接 nil
を代入する必要があります。
let value: Int? = nil
print(value) // これは 'nil' を出力します
変数はどのタイミングでどんな値になるか自由ですが、定数は初期化時に値が確定している必要があります。こういった違いを理解することが重要です。それでは、本題に戻り、暗黙的にアンラップされるオプショナルについてさらに詳しく見ていきましょう。 なので、nilかもしれないしnilじゃないかもしれない。そういう状況が延々と続いている中で、初期値がないんだったらnilでいいよね、あとはそれぞれのタイミングでどんな値になっているかを参照してみれば、nilじゃないこともあるかもしれない。こういう感じで十分意味が成り立つんですよ。
それに対してlet
というのはピンポイントで確定しちゃうので、必ずもう変化しないわけです。初期値を与えなくてもとりあえずnilね…という必要がまずないですよね。そもそも、その宣言したタイミングで値が決まるはずです。ちょっと語弊がありますけどね、確定したという意味があるので。ただ、一応宣言してそれが確定するというのが一連の流れとしてあるわけです。初期化フェーズが終われば値が確定されています。なので、どんな値が確定されるかというのは宣言した人間が把握しているので、その状況でnilをわざわざ書かない。この状況というのは逆に不安定です。この値は何なのか、作者はnilを肯定しているのか否か、肯定してるんだったら書けるでしょっていう感じですね。
これが定数let
と変数var
の特徴の違いを示す一例です。こういうところを見ていくと、let
とvar
の使い分けや、その背後にある世界観が見えてきて面白いです。例えば、let
でオプショナルな定数を作れるんですよ。オプショナルの関係で、例えば4行目にvalue
が入っていればオプショナルでも同じ話です。
オプショナルの説明を踏まえると、4行目がオプショナルじゃなくても、3行目がオプショナルだと、それがlet
の初期化フェーズで確定されるまでの間に値が使われると、アンラップ時にエラーが発生するという話ですね。この話はオプショナルに限定しないので、オプショナルを絡めると混乱しそうだなと思いました。確かにそうですね。
これが宣言で、ここが初期化。初期化フェーズが終わった後に確定した値になるんですよね。let
のときには完全にオプショナルとは無関係な言語仕様が動くので、混乱しにくいです。
一方、var
の場合は特例として、オプショナル特有の動きをします。var
に対して何か値を後で入れればその値が確定します。ただし、オプショナルでなければnilが初期値として設定されることがあり、オプショナル以外の変数であればプロパティラッパーを使わない限りこういう動きはしないです。
この話を聞いて混乱する方はいらっしゃいますか?自分も話を進めていて、何を言っているのか分からなくなってきましたが、とにかく3行目の場合は誰かが初期化しなければいけなくて、6行目の場合はオプショナル対策のためにイコールnilが補完されているという感じなんですよね。
オプショナルの定数の使い道については、特定の分岐が入る場合でしょうか。テストユニットのときや、何か外部プラグインによって初期化が変わる場合など。例えば、環境変数として値がある場合とない場合がある時などです。 実行環境が違うといろいろな状況が変わってきますね。さらに、それを何かしらのプロパティに渡す際、nilを調整するパラメーターとして使用することもあります。
例えば、比較だけが目的なら不要ですが、あるオブジェクトがあって、そのプロパティがオプショナルでない場合、let
でデフォルトの値を設定することができますね。例えば、let amount: Int?
がオプショナルで、デフォルト値をnilに設定する場合を考えます。そして、条件チェックの際にguard amount == defaultAmount
のようなガード文を使って判定します。このように、オプショナルな変数を使用するケースでは、比較相手もオプショナルにしないと、コードが適切に機能しません。
例えば、次のようなコードです:
let defaultAmount: Int? = nil
guard amount == defaultAmount else {
// 何かしらの処理
}
これは、amount
がデフォルトの値を持つ場合を想定しています。デフォルトの値がnilである状況では、変数もオプショナルとして扱う必要があります。例えば、オブジェクトがバンドルを持っていて、そのバンドルがbundleA
で、nilの場合はメインバンドルを使用するようなケースです。このような時に、オプショナルな定数が必要になります。オブジェクトが内部で異なる設定を持つ場合や、テストの際にモジュールが全く別のものになる場合なども同様です。
具体的には、次のようなコードになるでしょう:
public internal(set) var defaultBundle: Bundle? = nil
if let bundle = self.bundle {
return bundle
} else {
return defaultBundle ?? .main
}
このようにして、デフォルトのバンドルを定義して、nilの場合にメインバンドルを返すようにします。
また、テストの際にモジュールを差し替える場合もあるかもしれません。例えば、@testable import
でモジュールをテスト可能にすることができます。このような時に、異なる設定でテストを実行するためにデフォルトのプロパティを書き換えることがあるでしょう。オプショナルなプロパティが必要になる場面は多くはないですが、いざという時にその存在を知っていることが重要です。
まとめると、オプショナルな定数の利用は稀ですが、発想として持っていることが重要です。特にSwiftの言語仕様において、オプショナルの使い所や心がけるべき点などを押さえておくのが良いでしょう。例えば、何か他の方法があるのではないかと考えるきっかけになるかもしれません。そして、言語仕様を理解し、適切に使いこなすことが求められます。 けれど、これで単純に言うと、bar
のほうにオプショナルバリューがあって、基本的にはそのコードの可読性や、安全性を保っているという仕様が入っているんですよね。別の場合は特に特殊な仕様がないため、特に気にせずレシピとかエフェクトを使えばいいんです。Swiftはオプショナルを意識しないで済むように設計されています。
基本的に言語仕様の制御は行わないという感じです。
この例については、オブジェクトの例のほうがわかりやすかったかもしれませんが、混在しても大丈夫ということが面白いところですね。たとえば、a
がInt64
で、b
がInt16
でも、これを比較できるというのも興味深いポイントです。以前は形に厳格な言語でしたが、最近では初期化していない場合も柔軟に対応できるようになってきました。
このあたりがジェネリクスの利点を生かしたところで、バイナリインテージャーなども含めて==
で比較できるようになっています。これは非常に面白い発想で、コードの書き方にも多くの可能性をもたらしています。こういった多くの要素を含めながらコード書きをしていくと、きっと面白くなると思います。
さて、次にオプショナルについて説明します。Swiftにはアンラップされるオプショナルが用意されています。たとえば、オプショナルな変数を使っているとき、その変数が初期化された後は常に値を持つことが明らかな場合がありますよね。初期化が終われば、その値は維持されるという状況です。
例えば、iOSアプリケーションでIBOutlet
を使ってビューを構築するとき、awakeFromNib
が呼ばれた後は、そのプロパティが必ず値を持っていることが保証される場合があります。これを言語が型変わりして扱ってくれるのが、強制アンラップのオプショナル(IUO)です。
IUOを使うためには、通常のオプショナルの記述(?
)ではなく、宣言のタイミングで型の後ろにビックリマーク(!
)を付けてあげます。こうすることで、要素を参照するたびに強制アンラップのためのビックリマークを書かずに済みます。
この説明が上手くできたかなと思います。 先にびっくりマークを書いておくという解説、個人的には好きです。どういうことかというと、普通のオプショナルだと例えば Int?
型のバリューがあったときに、値が入っているかどうかは別として、この値を使用する際にはびっくりマークを書く必要があります。これが一般的なオプショナルの使い方です。
一方、強制アンラップのオプショナルは、先にびっくりマークを宣言しておくような感じです。実際には、オプショナル型に対する強制アンラップのフラグが付いた状態になっています。例えば、値1と値2があったとき、どちらも強制アンラップのオプショナルだった場合の型は同じです。「Type of 値1」と「Type of 値2」を比較してみても、どちらも同じ型になります。
昔はこの二つが違ったんですが、今は同じになっています。例えば、以前は implicitly_unwrapped_optional
という書き方がありましたが、今は使われなくなりました。その当時は型が違っていたのですが、現在では型が統一されています。エラーが出ることもありますが、それはコードやツールのバグの可能性があります。
今日は時間が来ましたので、この辺で終わりにします。もし質問があれば次回以降で受け付ける予定です。また、強制アンラップのオプショナルについても今後何回かに分けて詳しく見ていきたいと思います。
皆さん、今日はお疲れ様でした。ありがとうございました。