前回に予告しつつも辿り着けなかった The Basics
における オプショナル
の最後の項、自動で強制アンラップされるオプショナル
について眺めていこうと思うのですが、今回ももう少し、前々回の話題に関連した事柄で補足しておきたいことが見つかったので、まずはそれから話してみますね。それが終わってから、かつては "オプショナル型とは別の型" として存在していた 自動で強制アンラップされるオプショナル
の特徴を眺めていきます。どうぞよろしくお願いしますね。
今回もゆめみ社外に向けた参加者公募がされていて、ゆめみの外の人も幾名か来られての開催になります。
—————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #171
00:00 開始 00:32 前回の補足のおさらい 01:39 パターンマッチングも併用可能 07:25 関連値を考慮した判定の併用 08:05 実際に使えた体験談 10:06 if case による表現と見比べてみる 13:48 if case はよく使われる? 15:02 switch の方が安全 15:28 switch で default は避けたいところ 16:15 switch の網羅性による恩恵 19:05 default を使うか、全ての候補を網羅するか 19:59 if let を積極的に使う場面 24:15 case は switch で使うことが多い印象 25:20 関連値がある場面にも case は活躍 26:08 guard と case は相性が良いかもしれない 26:59 書きにくく感じるのは慣れの問題 28:19 if case ではコード補完が効かないのが難点 29:51 guard とオプショナルバインディングにおけるスコープ 31:51 おさらいとクロージング 32:25 次回の展望 ——————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #171
さて、始めましょう。今回は標準的なアンロックについて話す予定でしたが、その前に補足があります。ただ、前回も補足しましたので、2週連続の補足になります。
まず、お話ししたのは、複数のオプショナルバインディングと条件判定を併用できるということでした。具体的には、2つのオプショナルバインディングを使い、それぞれについて条件判定も加えることができるという話です。短絡評価という形で、最初の条件がfalseなら次に進み、次の条件もfalseなら次、といった具合にすべての条件を順に評価します。すべての条件がtrueなら、処理が実行されます。
前回の補足では、条件式とともに記述できるとお話しました。自信がなくなってきましたが、確かにそう補足しましたね。これに関連して、さらにポイントとして複数のオプショナルバインディングと条件判定を併用できるということを強調しました。つい2つだけに着目しがちですが、if文ではパターンマッチングも使えます。
例えば、構造体があって、その中にプロパティとしてisDebug
がBool
型であり、ステートが存在するとします。このステートは例として、イニシャライズ、ランなどがあります。何かしらの条件でステートを確認し、特定の処理を行うことができます。
struct Application {
var isDebug: Bool
var state: State
enum State {
case initializing
case running
case waiting
case terminating
}
}
let application = Application(isDebug: true, state: .running)
if application.isDebug {
print("Debug mode is active")
if case .running = application.state {
print("Application is running")
}
}
このように、if case
構文を使ってパターンマッチングを行うことができます。また、複数の条件を組み合わせることで、より柔軟なコードを書くことができます。具体的には、オプショナルバインディング、条件式、パターンマッチングを組み合わせて、一連の条件評価を行うことが可能です。
前回、短絡評価という言葉を調べましたが、それを理解したおかげで、より落ち着いて発想できるようになりました。デバッグモードの場合に特定の処理を行いたい場合など、これらの技術を駆使することで効率的にコードを書けるようになります。
この方法が特に役立つのは、複雑な条件評価を必要とする場面です。例えば、デバッグモード中のみ特定のプロセスのステートを確認し、さらにそのステートに応じて異なる処理を行いたい場合などです。
以上のように、Swiftでは複数のオプショナルバインディングと条件判定、パターンマッチングを併用することで、柔軟かつ効率的なコードを書くことができます。この知識を活用して、さらに実践的なプログラミングを行っていきましょう。 とりあえず、こんな風に、どちらでもいいのですが、こちらの方が良い感じですね。こうやって、単純な条件式とパターンを変えたいときに、分離しがちな部分をアプリケーションにまとめることができます。でも、これを書くと、結局同じような結果になりがちで、良し悪しですね。こうやってコードを書くと、if文のケースを使うと一般的にはスイッチ文を使う方が多いですかね。
アプリケーションのプロセス処理で、ケースを使う場合には、例えばcase.running.redvalue
のように書きます。個人的にはあまり使いたくないけれども、こういう感じですね。if文の中にスイッチを書いていくことはできないので、スイッチ文を使う方が見やすい場合もあります。スイッチ文を使うことで条件ごとの処理が明確になり、どの条件でどういう処理をするのかが分かりやすいです。
if文にスイッチを書くことはできないため、if文の条件式で分岐させるのは見やすいかもしれません。それでも、条件の書き方や処理の流れが少し複雑になることもあります。どちらの書き方が好みかや適切かは、人によるところが大きいですね。
たとえば、if文でのパターンマッチングに慣れていないと、スイッチ文に比べて可読性が低くなる可能性があります。しかし、スイッチ文を使うことで、候補が網羅されていない場合にコンパイルエラーになるという大きなメリットがあります。デフォルトケースを使わずに網羅性を確保することができるので、より安全です。
このように、スイッチ文にはメリットが多く、デフォルトケースを使わない方が良い場合も多いです。どちらの書き方が適切かはその時の状況や好みによりますが、安全性や可読性を重視するならば、スイッチ文を選ぶ方が良いでしょう。 で、網羅してないことがわかりますよね。これ、重要なところが抜けています。具体的には、ヌルのケースが抜けているんです。今、33行目から40行目のコードに対して、どこでヌルが混ざってくる可能性があるかというと、ここですね。オプショナルチェーニングによって、プロセスがない場合があるわけです。例えば、ヌルの場合どうするか。
現在の自分のイメージでは、ここにヌルがあった場合にも対応しているつもりでしたが、仮にこれがヌルの場合に特別な対応をする必要がある場合、問題が発生していた可能性があります。デフォルトで書かれていた場合にブレイクしていた可能性があるので、この2つを一緒に見逃していたことになります。
こういうことがあると、ちょっと怖いなと思います。自分はしばしばプラプラとコードを書き連ねてしまいますが、このような特殊な場面を忘れてしまうことがあります。デフォルトの方がそれ以外のケースに対応できていることが明確ですが、網羅していない部分を見逃すことがあります。
例えば、38行目のような書き方でもいいかもしれません。しかし、35行目か38行目のいずれかのケースしかないため、上か下かしかないことを考えると、どちらを選んでも簡単なように思えます。個人的には今の段階では38行目の書き方を推奨したいですが、将来はわかりません。
場合によってはifケースを積極的に使うこともあります。例えば、ランニング状態の場合、int
のバリューが取れるとしましょう。その時に、プロパティを取るためにオプショナルバインディングを使用するとします。その時に、オプショナルにしないといけない場面も出てくるでしょう。
よりわかりやすい例として、プラットフォームがバリアント型を扱う場合を考えてみましょう。この場合、Any
型の値を持つことができます。これをInt
型として取得するときには、元の値がAny
型なので、Int
型ではない可能性があります。これをオプショナルバインディングで処理します。
例えば、JavaScriptのバリュー型を例に取ると、case
ごとに異なる型を持つことがあります。以下のように型を定義するとします:
case number(Double)
case text(String)
case bool(Bool)
case undefined
case null
このように定義されたバリュー型からダブル型の数値を取得しようとするとき、あらかじめオプショナルバインディングを使って処理する必要があります。例えば、以下のように処理します。
guard case .number(let value) = value else { return nil }
return value
これは、値が取得できた場合にはその値を返し、取得できなかった場合にはnil
を返すというものです。
このように、明らかに一択しかないような場合にはguard case
やif case
を使ったほうがわかりやすいことがあります。どちらが良いかは、その場面によって判断する必要があります。 とりあえず、こんな感じです。条件の場合分けに「if」、「case」、「let」を使えるかなと思って、積極的にここでは利用しています。
コメントでご指摘いただいたのは、ケースの分岐についてですね。Switch文でも使うことが多いです。基本はそうなんですよ。デフォルトの場合も含めて、associated valueがある場合など、どの書き方でも完璧にカバーできないことがあります。そこで、コードの自動生成を考えるわけです。外部ツールによって安全なコードを担保してもらうやり方もありますよね。外部ツールを使う場合、仕様が変更されたり方法が追加された場合には、再度ツールを走らせる必要があるかもしれませんが、それでも手書きよりは良いと思います。
associated valueを持たせると、条件が難しくなることもありますが、Swiftのケース分岐はパワフルなので、パターンマッチングよりもメリットが多いこともあります。Switch文は全てのケース分を網羅する必要があるので、完璧な対比は難しいですが、それでも便利なことが多いです。
「if case」分は、場合によっては悪くない選択肢かなと思うので、この辺りにも目を向けて書いてみると良いでしょう。特にガード文は向いています。特定の条件を満たさない場合は、早めに処理を終了させるといった場面に特に向いているかもしれません。
パターンマッチングについて考えると、最初の頃は非常に気持ち悪い書き方かもしれませんが、慣れてくると使いやすくなります。こんな感じで書けるようになると良いです。Switch文で指定する値がパターンマッチングに適用されるイメージですね。if caseやガードケースでも、ケースに対してパターンマッチングを行い、それに値を適用するイメージです。
ガードケースやifケースについてですが、コードの保管が効かないのが厄介なところです。JavaScriptの型チェックのように、タイプによって自動保管が効きますが、Swiftでは難しい部分があります。ただ、これも練習すれば書きやすくなります。
ですので、この辺りを思い出しつつ、プログラミングをしてみてください。それぞれの良い書き方が見つかるかもしれません。
また、ガード文とオプショナルバインディングについても触れておきます。if文でオプショナルバインディングで生成した変数や定数は、if文の中だけで有効です。一方、ガード文で生成したものは、ガード文の後でも利用可能です。
こんな感じで、Swiftの言語仕様や使い方について学んでいきましょう。それでは、次回もよろしくお願いします。 これについては REEXIT
のセットで説明しているよとありますが、基本的には if
文のオプショナルバインディングの変数の特徴と guard
文の特徴をおさらいしているだけですね。なので、guard
を使っている人にとっては当たり前な動きを見せてくれますし、if
文のオプショナルバインディングの変数のスコープを押さえておけば大丈夫ということです。
たとえば、バリューが Int?
型のオプショナルとしてあったときに、if let value = optionalValue {}
のように書くと、この value
は if
文の正常ブロック(中)でのみ使え、else
ブロックでは使えません。もちろん、if
文の外でも使えませんという話ですね。これはこの勉強会の中でもお話したことです。
guard
文の場合も同じです。例えば、guard let value = optionalValue else { return }
のような書き方をすると、guard
文の中では value
は使えませんが、その後の正常なコードブロックでは使えます、というお話です。guard
文の約束ですね。この内容がスライドに書いてあっただけなので、そんなに難しいことではありません。
結論として、オプショナルバインディングは普通に使える、という話がメインです。このスライドに関しては、それを補足として書いてあったという感じのお話でした。
次回からは暗黙的にアンラップされるオプショナルの話をちょっとしていく感じになりそうです。これでオプショナルのセクションが全部話し終えることになるので、一通りオプショナルについては話し切れたということになると思います。
では、今日は2時間になったのでこれで勉強会を終わりにします。お疲れ様でした。ありがとうございました。