https://youtu.be/Eqa4om06HlE
今回も引き続き オプショナル
の初歩的な機能について眺めていきます。前回から オプショナルバインディング
を見はじめていて、それについてはそろそろ話し終えられそうなつもりだったのですけれど、思いのほか オプショナルバインディング
についてたくさん記されていたので、今回はそれについて確認していくことになりそうです。おおよそ話し終えた内容にも思えるのですけれど、せっかくですので書籍に沿って眺めていってみますね。
それと今回は前々回みたいに ゆめみ社外の人への一般公募
が行われますので、ゆめみメンバーではない人も幾名かオンライン参加されます。また、今日は私用で福岡を訪れた都合で、福岡からのオンライン開催になります。そんな機にせっかくなので天神にある Garraway F のイベントスペースを外にお借りして 実地でも少数集まっての配信
になります。どうぞよろしくお願いしますね。
——————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #155 00:00 開始 00:12 今回の展望 01:13 宣言前のプロパティーが使えてしまう問題 02:44 この問題はトップレベルでの仕様らしい 03:38 型に所属するプロパティーと比べてみる 04:49 変数にして後ろの方で変更すると? 06:12 後で宣言される定数の初期値が関数で設定されていたときの挙動 07:43 いろいろな型を初期化前に参照してみる 11:15 Playground ではない環境でも試してみる 12:56 初期化前の値は何で決まっている? 14:59 これはトップレベルだけで発生する問題 16:26 グローバルスコープ特有の挙動 17:23 メモリーだけ確保して未初期化の状態? 19:12 定数と変数でメモリーの確保方法に違いはある? 20:41 初期化前のプロパティーラッパーだと不正アクセス 24:05 @main であれば問題なし 25:54 初期化前でも参照可能 28:14 オプショナルの操作を単一の動作で行える 29:48 if 文とオプショナルバインディング 30:52 while 文とオプショナルバインディング 32:08 明らかな無限ループを検出してほしい 33:16 for 文はパターンマッチングとの併用になりそう 34:02 if 文とパターンマッチングを併用する例 35:03 オプショナルバインディングの書き方 35:57 右辺に非オプショナルな値を書くとエラー 40:15 クロージングと次回の展望 ———————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #155
今日は福岡のGalabay Fさんから配信を行います。今日の勉強会の題材はオプショナルに関する内容です。特に「if-let」すなわちオプショナルバインディングに注目します。
まず、前回の勉強会では一般向けの配信が行われなかったため、参加できなかった方もいるかと思います。前回、Swiftを使った際に奇妙な動作が発生することについて話したので、その報告から始めます。
前回の勉強会で発見された不思議な動作について説明します。例えば、オプショナルのパターンについて「switch」を使って処理を書くとします。以下のようなコードを考えます:
let value: Int? = 10
switch value {
case .some(let x):
print(x)
case .none:
print("Nothing")
}
このコードは期待通り動作し、例えばvalue
が10の場合、コンソールには10と表示されます。しかし、このときにバリューバインディングパターンのlet
をうっかり忘れた場合、本来なら変数x
が存在しないはずなのに動作してしまうという問題がありました。この点についてSwiftにバグとしてレポートしたところ、これは「switch」に限った話ではなく、グローバルレベルでも同様に動作するとの回答をいただきました。具体的なコードを示すと以下のようになります:
print(x) // エラーが出るべきところで出ない
このように、グローバルなコンテキストでも変数が宣言されていないのに参照できてしまいます。switch
文を削除しても、print
文がエラーを出さずに動作してしまう点は非常に興味深いです。
次に、以下のような関数を考えます:
func randomValue() -> Int {
return Int.random(in: 0...100)
}
この関数を使って変数の値を設定しようとすると、また同様の問題が発生します。具体的には、関数randomValue
の戻り値を変数に代入して、その変数を使おうとしても、あたかもその変数が存在するかのように動作してしまいます。これもまたSwift特有の挙動といえるでしょう。
そして、もう一つ興味深い例として、以下のようなコードがあります:
let value: Int? = 10
if let x = value {
print(x)
} else {
print("Nothing")
}
この場合、変数x
が正しくバインディングされ、10が出力されます。しかし、let
を忘れた場合には、本来存在しない変数x
が参照されてしまう可能性があります。
今回、このようにオプショナルバインディングの奥深い点について学んできました。次回も引き続きSwiftの言語仕様について掘り下げていきたいと思います。 前回説明したように、Swiftの変数の定義と使用についておさらいします。メイン関数もトップレベルに該当しますが、ローカルスコープでの処理もやはり重要です。
トップレベルの変数定義では、変数が定義される前に使用されることは許されていないはずですが、例外として、一部のパターンではそういったことが起こる可能性があります。たとえば、let
を使ったオプショナルバインディングの場合、後で定義している変数を先に使ってしまうことがあるのです。このようなケースでは注意が必要です。
メイン関数におけるスコープはグローバルスコープとは少し異なります。グローバル変数と呼ぶにはやや微妙なところがありますが、基本的には特殊なグローバルスコープにいると考えても良いでしょう。そのため、コントロールしやすい一方で、コンパイルエラーにならない場合もある点に気をつけなければなりません。
さらに、メモリアロケーションに関しても、変数の中身をゼロフィルされている場合が多く、それが初期化の一環として正常にアクセスできる状態になっていることも確認されています。しかし、中には初期化が完全に行われない場合もあるので、万全を期すためには自分で明示的に初期化することが推奨されます。
また、let
とvar
の違いについても議論がありましたが、基本的には以下のようなポイントを押さえておくと良いでしょう。
let
は変更不可の定数を定義する。var
は変更可能な変数を定義する。let
の値はコンパイル時に確定するが、var
の値は実行時に変わる可能性がある。
そのため、let
とvar
の使い分けは、コンパイルタイムで固定したい値と、ランタイムで変更する可能性のある値とを区別するために重要です。
以上が今回の勉強会の内容です。引き続き、自分でコードを書いて試すことで、理解を深めていきましょう。 ブロックで動いている部分もブロックで遅いですね。確かに特殊なケースですね。ちょっとプロパティラッパーを試してみたほうが良いかもしれません。
プロパティラッパーで試してみましょう。例えば、@propertyWrapper
を使ってみたいんです。この方法で結果がどうなるか試してみます。まず、@propertyWrapper
を使って、例えば Int
型の値を保持するプロパティを作成します。次に、そのプロパティに特定の処理を追加して、それにフェッチアウトの処理を加えてみます。しかし、日曜日とかに呼ばれた場合には何もしないようにしておきます。
一応、トップレベルにこのプロパティラッパーを入れるときに、@propertyWrapper
ではなく @property
として入力したい場合も試してみます。以前に行ったように、最初に Int
型の値が入力されたとします。その事情を考えると、トップレベル関数が少し特殊な性質を持っていることが伺えます。
これを実行していたのですが、@propertyWrapper
が呼ばれるときの状況がうまくいかない場合もあります。そのため、プロパティラッパーを static
プロパティとして持つようにしました。また、インスタンスメンバーではなく static
メンバーであるべきかを考慮しました。
実際のオブジェクトを作ってプロパティラッパーを管理する方法も検討しましたが、この方法では準備が整っていないという結論に至りました。今後、トップレベルでプロパティラッパーが利用できるようになる可能性があります。
トップレベル関数(例えば main
関数や @main
属性など)を操作する際には慎重になる必要があります。@main
属性は SwiftUI のアップデートで結構使われるので、それを考慮してコードを整える必要があります。static
メイン関数を作成し、その中に必要な処理を書いていく形となります。
具体的には、例えば SwiftUI のアプリケーションを作成する場合、@main
属性でアプリケーションエントリーポイントを定義します。こちらの方が関心が高いですね。もちろん、コードを削除しても問題ない部分もあるので、その辺りは自由に調整していくことができます。
準備は整っていないかもしれませんが、プロパティラッパーが使えるようになったときのためにコードを整えておくことが重要です。print(x)
や、変数だけ宣言して実行するプログラムでも全体がちゃんと動くようになるでしょう。
このような使い方については慎重に行うべき部分もありますが、適切に応用すれば非常に便利な機能です。前回のお話に引き続き、重要な情報を共有できました。
では、残りの時間で Swift のプログラミング言語について話を進めていこうかなと思います。 これで Swiftプログラミング言語の公式ドキュメントの話をスライドに簡単にまとめています。これを改めて見ていきましょう。
前回はオプショナルバインディングが使えるよという話をしました。一時的な定数や変数に使えるよという話でした。オプショナルバインディングは if
や while
と合わせて使うことで、オプショナルの値を検証して、その値を定数や変数に抽出します。これを簡易な動作で行えるのが特徴です。この簡易な動作が一番嬉しいところですよね。
たとえば、if
分で使える、while
分で使えるということですが、要は「プール」としての結果が得られるような条件式で使えるということです。そうすると if
と while
だと、もうパターンマッチングになっていって、プールというよりもうちょっと広いかな。オプショナルバインディングはそこまでにして、それ以外の純粋にプール的な部分って if
と while
かなという気がします。
具体的には、if
や while
と併用して単一ブロックが使えるところも便利です。前回もお話したとおりですが、let value = optionalValue
のように、まずオプショナル型であったときに、if let value = optionalValue
と書くことで、この optionalValue
が nil
でない場合、その値をローカル変数 value
に代入して、そのブロックの中が動くという動作を簡単に実装できます。これによって、もし optionalValue
が nil
であった場合の処理も else
ブロックで対応できます。このような機能がオプショナルバインディングに使えるという形になっています。
while
文でも同じ感じです。while let value = optionalValue
と書くことで、特定の条件が nil
でない間はそのブロックが繰り返し実行されます。注意点としては、while
文の場合、条件が nil
になるように適切に更新する必要があります。例えば、value
が nil
になるまでループが続くような設定が必要です。そうしないと無限ループになってしまいます。
for
文については少し違うかもしれませんが、これはコレクションに対してパターンマッチングを適用したい場合に該当する話となります。そのため、条件として判断するか値を取るかで違ってきますが、要は値を取り出してパターンマッチさせるという発想になるかと思います。
このように、Swiftのオプショナルバインディングやパターンマッチングは非常に便利で、効率的なコーディングをサポートしてくれます。また、これらの機能を使いこなすことで、より安全で読みやすいコードが書けるようになります。 雰囲気がwhile
とすごくよく似ていますけど、これはパターンにマッチした場合にはこうする、みたいな条件式ですね。while
は値を取り出してきて、うまくマッチさせて抽出するというイメージです。
こんな感じで、やっぱりオプショナルバインディングとオプショナルパターンの若干違うところを押さえておくと、良い感じに Swift のコードが書けるようになりますね。次に行きましょうか。次は何でしょうか。
次のスライドに移ります。if
文についてです。これは散々やってきたので大丈夫でしょう。if let
と書いて、その後にブロックの中で使うローカル変数の名前を書きます。右辺がオプショナルバリューであることが前提ですね。ちなみに、オプショナルではない値を書くとエラーになります。
例えば、let x: Int
がオプショナルではない時に、if let y = x
と書くとエラーが発生します。これは Swiftのプレイグラウンドでもエラーで指摘されます。だから無理やりオプショナルではない変数に対してオプショナルバインディングの構文を書く場合には、オプショナルにキャストする必要がありますが、あまりやることはないですね。
また、文脈全体で見たときにオプショナル型にしたいということもあるかもしれませんが、基本的には自然なオプショナルパターンやオプショナルバインディングを使うべきです。例えば、let x: Int?
がオプショナル型で、その中にlet z
を使う場合、オプショナルバインディングを書くと、意味のある判定ができます。
let x: Int? = 2if let y = x { print(y) } else { print("nil") }
このようにした場合、x
がオプショナル型でないなら何の意味もないことになりますので、注意が必要です。
最後に、オプショナルバインディングとエラー処理は相性が悪いことがあるので、実際に使ってみるとその問題点に気づくことがあると思います。大体のところ、エンディングとオプショナルは相性が悪いので、あんまり使わないほうが良いですね。
今日は時間もここまでなので、次回はタイミングの使用例などを含め、もう一度おさらいします。それでは、今日はここまでにしましょう。何か質問があればどうぞ。
オプショナルバインディングについて他に面白い事例があればぜひ教えて下さい。では、今日はこれで終了します。お疲れさまでした。ありがとうございました。