本日はまず、 自動で強制アンラップされるオプショナル
についての最後のところ、どんなふうにそれを扱っていくべきかについて確認します。それを見終えて特に何かオプショナルについて話し忘れたことがなさそうなら、引き続き エラー処理
の話に入っていきます。どうぞよろしくお願いしますね。
今回はゆめみ社外な人の参加なしでの開催になりそうです。
——————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #177
00:00 開始 00:42 暗黙アンラップなオプショナルの基本 02:20 暗黙アンラップなオプショナルを nil 判定するのは不自然 07:40 Assert でなら nil 判定も有り得る? 09:57 そうなると IUO 属性を型推論が解除する必要性は? 10:28 暗黙アンラップなオプショナルの型の扱い 11:56 型推論で通常のオプショナルとして扱う意味は? 14:09 階層の途中を変数に取り出すことについて 18:54 なぜ普通のオプショナルとして扱おうとする? 21:44 IUO 属性という特別扱い 24:03 暗黙アンラップとオーバーロード 25:00 暗黙アンラップなオプショナルの戻り値 27:25 引数に渡すときの扱いの都合? 28:04 初期化時に値を決められない場面での活用 29:44 プロパティーラッパーによる表現方法 33:13 クロージング ———————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #177
では、始めていきましょう。今日は Swift のオプショナルについて学びます。スライドは1枚なので、残りの部分を見ていくことにしましょう。
現在、暗黙的にアンラップされるオプショナルについて話しています。この部分は初期化フェーズに関連しています。初期化フェーズでは初期化が終わっていない変数もありますが、実際に参照するまでには初期化が完了しています。そのような変数に対して、暗黙的にアンラップされるオプショナルを使います。
このために、型の後にびっくりマーク !
をつける特別な型が用意されています。この型の利点はいくつかありますが、通常のオプショナルとして使える一方、必要なときに自動的にアンラップされるという特徴があります。
前回の話では、型推論による代入式のときに特殊な動きをすることがあるため、注意が必要だということでした。それ以外は通常のオプショナルと同じで、nil
判定やオプショナルバインディングなども可能です。
次のスライドでは、まだ話していない内容がありますが、暗黙的にアンラップされるオプショナルを扱う際のnil
判定やオプショナルバインディングについて触れています。オプショナルバインディングはケースによっては使用することがあり得ます。しかし、基本的には暗黙的にアンラップされるオプショナルを使う時点で、その使い方に疑問を持ったほうがよいです。
『The Swift Programming Language』にも記載されていますが、どこかの時点で nil
になる可能性がある場合、暗黙的にアンラップされるオプショナルは使わないべきです。つまり、参照する時点で必ず値があることを保証する必要があります。これはプログラマーの責任であり、宣言する側の自己責任で行うべきです。
このため、暗黙的にアンラップされるオプショナルを使う際は nil
判定をしないのが基本です。このルールを守る限り、オプショナルのように扱うのは適切ではないことを理解しておく必要があります。実際、『The Swift Programming Language』にもこの記述があります。
まとめると、暗黙的にアンラップされるオプショナルを使うときは、常にその変数が nil
にならないことを確認し、必要に応じて別の方法(例えば、通常のオプショナル)を検討するべきです。 自分はこのスライドを作って、初めて詳細を見たんですけど、あまり注目されない部分も考慮しながら進めました。例えば、オプショナル(Optional)についても、予期せず nil
になってしまうことがあるかもしれないと考える必要があります。誰かが通常のコードで nil
を代入する可能性も否定できないですからね。
この場合、nil
判定が必要になったり、オプショナルバインディングが必要になることもあるかもしれません。どちらが良いのかについては意識して運用するのが良いでしょう。前のスライドで nil
をチェックする方法や、それがドキュメントにどう記載されているかが確認できます。
昔はオプショナルは強制アンラップ(Implicitly Unwrapped Optional)として定義されていて、コメントで見れたんですけど、現在はその形式がなくなっています。そのため、オプショナルが暗黙的にアンラップされるケースについての情報には自然にはアクセスしづらいという課題があります。
通常のオプショナルしかない場合、nil
チェックをすることが前提ですが、実装ミスを防ぐためにも基本的には nil
を設定するようなことは避けるべきです。確実に nil
にならないことを意識して実装するのが望ましいですね。
前に触れたスライドを総合してみても、この問題に関する基本的な考え方は変わらないようですね。オプショナルは通常のオプショナルとして扱える特性を生かすと、アサート(assert)を利用することもできます。たとえば、オブジェクト
が存在しなければならないシチュエーションで、プリペアされているかどうかを確認する際にアサートを使ってチェックすることができれば、デバッグ時に有利です。
もちろん、このように特定の場面で nil
判定が必要ないように設計するのも一つの手です。昔はローカル変数としてオプショナルを扱う際に、オプショナルじゃなかったらどうするかという前提があったので、現在のようにオプショナルとして扱えるシステムはある意味では安心とも言えます。
ただし、あくまで暗黙的にアンラップされるオプショナルの値を使うときには、Swift がまずそれを通常のオプショナルとして扱い、オプショナルとして扱えない場合には強制アンラップするといった特定のルールに基づいて動作します。
このように、暗黙的なアンラップが許されるケースや、通常のオプショナルとして扱える場面を適切に理解することが大事です。こんな感じで基本的なイメージを持ちながら進めると良いかと思います。 もう一回スライドに戻ります。最後に紹介した部分についてですが、ニル(nil)の可能性がある場合には、オプショナルを使わないことが基本です。もしオプショナルを使う必要がないのであれば、それはオプショナルではなくすべきです。
例として、IBOutlet
を考えてみましょう。例えば、以下のようにIBOutlet
にUILabel
を設定したとします。
@IBOutlet weak var textLabel: UILabel!
この場合、textLabel
の内容を表示する際に、次のようにアクセスできます。
textLabel.text = "Hello, World!"
ですが、長い処理が必要な際には、次のように変数に一度受け取ったりします。
let label = textLabel
label?.text = "Hello, World!"
強制アンラップ(force unwrap)を使わなければならない場面では、コードが煩雑になりがちです。プログラムの設計において、ローカル変数に受け取ってから操作をすることが減った理由の一つに、構造体が参照型であることや、強制アンラップの問題があります。たとえば、強制アンラップを使うと次のようになります。
textLabel!.text = "Hello, World!"
もし、このようなアンラップが必要ない設計にすることで、コードがより直感的で安全になります。
Swift の場合、メンバー変数へのアクセスも容易になったため、あえてローカル変数に取る必要が減りました。また、プログラムを書く際のAPIデザインガイドラインに従うことで、主体を意識したコードを書くことが重要です。たとえば、UILabel
のtext
プロパティにアクセスする際、以下のようなエクステンションを利用することも考えられます。
extension UILabel {
var nonOptionalText: String {
get {
return self.text ?? ""
}
set {
self.text = newValue
}
}
}
このようにエクステンションを利用することで、オブジェクトに対する操作が簡潔に記述できます。それにより、ローカル変数に受け取らずとも操作が可能になります。
たとえば、以下のようにエクステンションを使用できます。
textLabel.nonOptionalText = "Hello, World!"
以上のような理由から、オプショナルや強制アンラップが必要な場面を減らし、コードの可読性と安全性を向上させることができます。
このように考えると、IBOutlet
が強制アンラップのオプショナルであったとしても、それに対する不安が減ります。ただし、このようなデザインがなぜされているのかについては、まだ理解が進まない部分もあります。たとえば、Objective-C ではデリゲートメソッドでオプショナルが多用されており、その影響があるのかもしれません。 オプショナルの優先度が問題になった際に、暗黙的にキャストされる機能が追加されたんです。これによって、元の型があった場合でも、暗黙のうちにアンラップされるようになりました。
以前は、オブジェクティブCでリクエストマークされたメソッドと同じ方法でSwift側でも判定されていました。例えば、あるオプショナルな値を渡したときに、そのメソッドが呼び出されないという問題があったのですが、今は別途判定されるので、分離された機能として働きます。
具体的に言うと、オプショナルの型としてはまずオプショナルが優先されるんです。しかし特定の状況では、暗黙的にアンラップされるという二つの機能に分かれています。この分離により、型情報がシンプルになり、複雑性が減りました。ですので、暗黙的にアンラップされるタイミングが幾つかの現象として存在しても、型情報自体は簡潔になりました。
この機能分離によって、オプショナルを基本とする考え方が強化されました。オプショナルとアンラップのタイミングは属性として取り扱いますが、オプショナルの方が優先されるのです。つまり、基本的にはオプショナルとして扱うけれども、特定のタイミングで暗黙的にアンラップされるということですね。
APIの設計やインターフェースの観点から考えると、これによりコードがシンプルになり、マッチしやすくなります。ランタイムでのエラー回避や解決も期待できますし、これはAPIの整合性を保つためにも重要です。
一方で、オーバーライドされるメソッドやシグネチャ判定の際に矛盾が生じることも減少し、オプショナル型がより扱いやすくなります。例えば、整数型を返す特定のアクションで、時折nil
が入るような状況にも対応しやすくなります。
オブジェクティブC由来のインターフェースでは、値を返す際にオプショナルになることが多かったです。これにより、型が煩雑になってしまうことがありました。しかし、Swiftの暗黙的なアンラップ機能を使うことで、オプショナル型の複雑さが緩和されます。
結論として、オプショナルが優先される状況では、暗黙的なアンラップが特殊なケースとして捉えられるため、型の扱いが一貫性を持ちつつシンプルになるということですね。 分かりづらい点がいくつかありますが、途中で「見る返し」になってしまう場面があるとします。これは良くないことですので、オプショナルを使用する際には、完成段階で返ってきた時点で使う必要があるのです。そのため、「見る返し」が返ってくるときには使うものではないということですね。
初期化時に特定の値を入れられないということがありますが、これが不法上の問題になり得るため、推定される値を使用する場合があります。ただ、Swiftのみに限れば必ずしも必要ではないかもしれません。リミットが適切に設定されていれば、一時的に避けられることが多く、そのような場合には避けるべきだという考えは重要です。
さらに、後で話に出てくる参照型や循環参照の問題、デリゲートなども関係してきます。これらを管理する際に強制アンラップのオプショナルが初期化フェーズでは決まらないことがあります。この場合、使用する際には強制アンラップのオプショナルを使用することもあり得ます。
最近、プロパティラッパーが登場しましたが、これを使うと初期化時には必ずオプショナルではないものを作ることができるようです。自分でプロパティラッパーを作成し、例えばIUオート
という名前にして、ラップバリュー
として確定させることができます。ゲッターの中でローカルプライベートのバリューを持ち、ゲッターの際にはアンラップされたバリューをリターンする。このような方法で、プロパティラッパーを使えば初期化時に確実に初期化されていることを保証できます。
ただ、プロパティラッパーはグローバルには使えないなどの制限もあるため、混乱しないようにする必要があります。後で正しいコードを共有しますが、この方法を使えば、イントーカットバリューが自動的に適用されるようになります。プロパティラッパーの特性をしっかり理解して使うことで、予期しない動きやエラーを防ぐことができます。
暗黙アンラップのオプショナルが微妙だと感じる場合、プロパティラッパーを使用して確実に初期化されるようにするのは良い方法かもしれません。闇雲に予期アンラップのオプショナル機能に頼るのではなく、意図的に制御することが大切です。
では、ここまでにしておきましょう。お疲れ様でした。ありがとうございました。