https://youtu.be/WSrn0fgsF6g
ここのところ オプショナル
の強制アンラップ周りについていろいろと寄り道して眺めてきましたけれど、今回こそは オプショナル
の初歩的な機能 オプショナルバインディング
あたりのところから眺め始めてみますね。とても登場頻度の高い機能なので、Swift に馴染みない人にもちょうど良いですし、馴染んでいる人にも逆に親しみすぎて気を回す機会は少なかったりするところと思うので、せっかくの機会にオプショナルバインディング周辺を見渡してみましょう。
それと今回は、ゆめみ社外の人への一般公募はなかったようなので、基本的に社内メンバーのみでの開催になりそうです。どうぞよろしくお願いしますね。
—————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #154 00:00 開始 00:46 オプショナルバインディングで変数を使う 03:08 オプショナルバインディングの中でのみ有効な変数 04:59 引数を変数としては宣言できない 07:02 引数は inout で書き換え可能になる 07:30 & は参照渡しではない 09:47 if var 構文は残されている事実 10:44 for でも変数に値を受けられる 13:16 for で let は書かない理由は? 15:03 switch のパターンマッチングと比べてみる 16:09 トップレベルでは後で宣言するプロパティーを使える 20:14 初期化前なのに値が使えている? 22:09 後で設定した値が設定前に使える? 24:06 初期化前でも、初期化後の値が参照できるか確認 25:21 関数で初期化する予定の値を先に使うと? 27:11 クロージングと次回の展望 ——————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #154
しばらくオプショナルの基本的なところを見てきましたが、オプショナルアンラップの部分で少し話が逸れてしまいました。それでも大体のところは理解できたので、今日はオプショナルバインディングについて戻り、ゆっくりと見ていこうと思います。
まず、オプショナルに値が含まれているかを確認し、含まれていればその値を一時的な定数や変数として使うためにオプショナルバインディングを利用します。これについてはよく知られた内容ですね。この「一時的な定数や変数として」という解説がポイントで、特に変数については見落としがちな部分でもあります。
一般的にSwiftではローカルスコープで変数を使うことがあまりないため、オプショナルバインディングで変数が使えることを忘れがちです。例えば、以下のコードを見てください。
let value: Int? = 10
これはオプショナルとして準備されています。次に
if var unwrappedValue = value {
unwrappedValue += 5
print(unwrappedValue)
}
この場合、unwrappedValue
は変数として使えるという点が重要です。このコードではunwrappedValue
が15
となり、プリントされます。ただし、これはローカルで変数unwrappedValue
を確保しており、外部スコープのvalue
には影響を及ぼしません。
そのため、ローカル変数unwrappedValue
の範囲外では、元のvalue
は変わりません。この点に注意しておくことが重要です。例えば、次のコードでは
let value: Int? = 10
if var unwrappedValue = value {
unwrappedValue += 5
print(unwrappedValue) // ここではunwrappedValueは15
}
print(value) // ここではvalueはオプショナルで10のまま
となります。これで、ローカル変数の使い方とその影響範囲が理解できると思います。
また、昔は関数の中でも次のように変数として扱えることがありました。
func doSomething(value: Int) {
var value = value
value += 5
print(value)
}
このコードはSwift 3以前では機能しましたが、その後変更されました。理由は、ローカルにミュータブルな変数として宣言するか、引数の値が呼び出し元に渡るかが分かりにくかったためです。現在ではこのような書き方はできませんが、オプションバインディングなど他の手段を使って同様の効果を得ることができます。
結局、オプショナルバインディングの変数を使う方法も覚えておくと、コードがより整理され、効果的に管理できるようになるでしょう。ぜひ頭に入れておいてください。 ちなみに、引数としてインアウトのパラメーターを書く場合、例えば「value += x
」のような操作ができるようになりますが、これは外部に影響を及ぼします。ここは注意が必要ですね。渡すときには、引数が可変である必要がありますので、これを示すために&
が必要となります。&
を強制するのは言語仕様として覚えておくべきポイントです。
参照渡しするわけではないですが、参照渡しっぽく見えるように&
を使い、さらに変数を定義するときにはvar
にしないといけません。これを通すことで、値が変更可能になります。ただし、オプショナル型を渡すことはできません。データサイズが違うため、当然のことです。
具体的に言うと、例えばx
をInt
型で10
にしてvar
をつけてから渡すと、結果としてx
は20
になります。インアウトが必須となるパラメータについても、言語仕様としてうまくできています。var
を使用することで、ローカルだけでの処理なのか否かが明確になります。
Swiftの言語仕様では、インアウトで使うことによって、変数が入出力を兼ねていることが明確になります。使う側も、&
を使うことでミスが少なくなる設計になっています。これにより、うっかりミスが防げるのが良い点です。
次に、if let
構文についてですが、let
ではなくvar
を使う場合もあります。この部分も活用次第で便利に使えるでしょう。技術的なパラメーターとは違い、仕様が隠されている部分もありますが、Swiftが提供している構文自体は問題なく、混乱するものではないでしょう。言語設計者側でもそのように判断していると考えられます。
また、if let
でのオプショナルバインディングについては、if let
の後に変数を並べて書くので比較的分かりやすいです。しかし、for
文ではどうでしょうか。例えば、let value = 1...4
と書くとしましょう。for
文の後では、let
を使うことができません。試してみると、「let
パターンとして扱われる」というエラーが出てきます。結局、そのままの形で使うことはできず、for
文の後にパターンマッチングとして利用することが求められます。
for
文でのlet
の代わりにvar
を使うと、変数がミュータブルなコンテキストで動作することになります。歴史的には、let
が許容されていた時期もあったかもしれませんが、現在の仕様では許容されていないようです。
こんな感じで、Swiftの言語仕様は便利であり、活用することで効率的にコーディングができるようになります。これからもどんどん使い慣れていきましょう。 Swiftでは、通常は変数の再代入を避けるためにlet
を使いますが、必要ならばvar
を使うこともできます。その際、let
をデフォルトとし、特殊な場合にvar
を使うというのが一般的なガイドラインです。この考え方により、コードのバランスが取れると感じるかもしれません。
一方、条件分岐内での変数宣言に関しては、if let
を使ってオプショナルの値をアンラップすることがよくありますね。これにより、nil
チェックと非nil
値の取得、変数宣言を一度に行うことができます。この使い方には自然な感覚があると思います。
また、switch
文内でもパターンマッチングを利用することができ、ここでもlet
を使うことでオプショナルの値を安全に取り出すことができます。たとえば、次のようなコードです。
let optionalValue: Int? = 10
switch optionalValue {
case let x?:
print(x)
default:
print("nil")
}
この例では、オプショナルのoptionalValue
が値を持っている場合にはx
としてアンラップされ、print(x)
が実行されます。しかし、このoptionalValue
がnil
の場合はdefault
ケースが実行され、「nil」がプリントされます。
Swiftのプレイグラウンドでの特殊な挙動も興味深い話題です。変数が未初期化なのに存在が認識され、エラーが発生しないケースもあるようです。例えば、次のようなケースです。
var y: Int
switch 42 {
case let y:
print(y)
default:
break
}
このコードは普通にコンパイルが通るようですが、実際にはy
が初期化されていない状態です。このようなケースは、プレイグラウンド特有の挙動かもしれません。しかし、通常のコンパイル環境では、変数が初期化されているかどうかを確実にチェックする必要があります。
以上のように、Swiftの変数宣言やパターンマッチングに関しては、いくつかの規則や注意点がありますが、それらを理解することでより安全で効率的なコードを書くことができます。 そうですよね。そして、それに加えて、値がないだけじゃなくて、前にある変数が前にあると値が y
がないということが、やっとここで認識されるんですよね。さらにもう一段上の挙動がある感じです。面白いですね、これ。
これがないとビルドが通らないのに、これがあると上にあると値がない。面白いですね。もしかすると、この前のバグと関係があるかもしれません。そうかもしれないですね。ほんとだ。じゃあ、そのうちしれっと治るかもしれませんね。
うん、テーファーについては若干このコードに実害がありましたけど、これは実害がなさそうですね。でも、なさそうとも言えないか。例えば、「スコープの後ろにある変数を取っちゃう」といったことが起こると怖いですね。絶対にここがパターンマッチしないケースになっているのに気づかないというのは、なかなか恐怖なバグですから、気を付けないといけないですね、怖いですね。
もう一度確認のために値設定で試してみましょうか。これと一致したかな、一致したので、これが動いていますね。なるほど、見てみましょうか。デフォルトは0になりました。これは恐怖ですね、やばいですね。
最近幸いなのは、長いコードを書くことがあまりないですが、ちょっと入り組んだコードで長いコードを書いたときに、「スコープで使う let y
を取ったけど、後で y
を使う」といったこともあります。この y
はスコープで終わるので、安心して y
を使っているわけですが。そのときに let
を忘れるだけでちょっと注意ポイントになりますね。
話はかなり脱線しましたけど、バリューバインディングパターンで let
を書かなきゃいけないというお話がありましたね。バランス感のあるお話です。それで、ここがバリューバインディングパターンでは var
もあって、これで実行していくこともできるということですね。つまり、バリューバインディングパターンでも let
と var
は対等に使えるというお話です。
例えば、バリューバインディングパターンを忘れたバージョンを書いてみます。そして、y
をここで 10
と設定しますね。これで一致した際の y
を表示するとどうなるのか。まだ初期化していないですよね。これでやってみると、10
と出ましたね。
じゃあ、これを違う値にするとどうなるのか。デフォルトに行きますね。このときデフォルトで y
を表示するとどうなのか。おっと、ここにも違う初期化がされています。やばいですね。ここで初期化前に使用しました。
ちょっと面白いことを思いつきました。例えば、func getValue() -> Int
を定義し、その中で let
で初期化します。戻り値として Int
を返します。そして return z
とします。コードを実行してみると 10
が返ります。このとき、let
を使わずに別の値を返すと、0
になります。何か変なことが起こっていますね。
もしかすると、イコールで値を設定せずに 0
になるのかもしれません。デバッグの際に 0
をフィルしているのかもしれませんが、リリースビルドでどうなるかも興味深いポイントです。面白いですね。
今日はこの辺で終了にしますかね。次回は、オプショナルバインディングの話をしつつ進める感じになるかと思います。スライドもまだ見終わっていないので、引き続き次回も進めていきます。今日はこれで終わりにします。お疲れ様でした、ありがとうございました。