前回から自動で強制アンラップされるオプショナル
の話に入っていきましたけれど、その中で最後に少しだけ口にした気がする「自分自身で制御できないコードにならないように」みたいなところを、まずは具体的に眺めてみようと思います。その上でもう少し特徴面での基礎的なところを確認していく予定です。どうぞよろしくお願いしますね。
今回もゆめみ社外に向けた参加者公募がされまして、ゆめみの外の人も幾名か来られての開催になります。
———————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #174
00:00 開始 00:23 前回の補足と今回の展望 01:32 初期化後は値が確実に存在するとき 02:57 必要ないならオプショナルは使わない 03:42 局所スコープで値が定まるなら確定初期化 04:13 @IBOutlet まわりで使われる例 07:06 通常のオプショナルだと煩雑になる 08:22 値が存在しなかったときには実行時エラー 09:16 暗黙アンラップなオプショナルに窺える利点 10:17 普通のオプショナルでも確実に値があるとされるとき 11:14 闇雲に使わないように注意 11:58 暗黙アンラップなオプショナルが必要な場面 13:57 self は初期化が終わるまで使えない 16:01 期待しない動作でも実行時エラーを避けた方が良い可能性 17:35 Storyboard 自体がリスクの可能性 18:33 実装ミスが起こったときの影響度で判断 19:58 assert を活用するアイデア 21:54 プロパティーラッパーで assert を挿入できる? 29:10 独特なルールの濫用には注意 30:56 lazy var と didSet は併用可能に 31:41 lazy var の初期化フェーズは初期値の設定時 31:56 クロージングと次回の展望 ————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #174
はい、では始めていきましょう。今日はオプショナルの続き、特に「強制アンラップされるオプショナル」についてです。前回からこのトピックに入りましたが、少し補足しておきたいことがあります。強制アンラップのオプショナルにnil
を代入できるけど、初期化後はそのnil
がもう使えないという状況で使われることがあるんです。
例えば、初期化をイニシャライザーのタイミングで完了させられないけれど、少し遅れて初期化を行い、その初期化が完了すれば安心して使えるという状況で、この強制アンラップのオプショナルが使えるという話をしました。しかし、これに頼りすぎると初期化が予測できなくなるので注意が必要です。
では、スライドを確認してみましょう。前回のスライドでは、「強制アンラップされるオプショナル」はプログラムの構造的に初期化後は常に値を持っているのが明らかなときに使うと説明しました。確かにそうなのですが、具体的な状況についても話しておくべきです。
例えば、初期化後に必ず値を持つなら、その初期化のタイミングで初期化しちゃえばいいわけです。基本的にはこれが自然です。しかし、もし初期化のタイミングを分けたい場合には、確定初期化のような方法もあります。このように強制アンラップされるオプショナルの使いどころをしっかり見定めて使うことが重要です。
具体例として、IBOutlet
を用いたUIコンポーネントの例を挙げます。例えば、UIコンポーネントのクラスUIViewController
があります。この中で、@IBOutlet
を用いてUILabel
を宣言することがあります。この場合、ストーリーボードの中でインスタンス化され、ストーリーボードと関連付けられたときに初めてインスタンスが割り当てられるため、初期化時には値を持っていません。
@IBOutlet weak var titleLabel: UILabel!
このような場合、初期化のタイミングではこのUILabel
は初期化できません。ストーリーボードを使用する場合、初期化が若干遅れるので、nil
が入ることを避けるために強制アンラップのオプショナルを使うことがあります。ビューが読み込まれた後に必ずインスタンスが設定されるので、その時点で安全に使うことができます。
したがって、強制アンラップオプショナルを使う場合は、その使いどころに注意し、適切に使うことが大切です。 Swiftのオプショナルを使う際に、強制アンラップの利点について説明しますね。強制アンラップを使用することで、オプショナルの値を取り扱う際に安全にアクセスできます。例えば、オプショナルの値を利用したいときに、オプショナルチェーニングを使うと、その値がnilであった場合でもエラーを回避しつつ処理が進行しますが、一方で、意図しないnilが含まれていた場合でもエラーにならずにスルーしてしまうという問題があります。これにより、設計ミスやうっかりミスが見逃されやすくなるんですね。
具体的には、ストーリーボードで設定すべきタイトルラベルに値が設定されていなかった場合、オプショナルチェーニングを用いるとエラーが発生せずに処理が継続してしまいます。しかし、強制アンラップ(!
)を用いると、そうした場合にアプリがクラッシュし、問題箇所が明確にわかります。開発中のデバッグ段階でクラッシュしてくれることで、リリース前に問題を発見しやすくなるのです。
このように、強制アンラップは意図的に使うと効果的です。絶対にnilでないと確信している場合や、プログラムの特定の段階で値が存在することを保証できる場合には、積極的に使っていくのが良いでしょう。
また、オプショナルのもう一つの使い方として、遅延初期化があります。この方法は、初期化が遅れる可能性があるプロパティに対して便利です。例えば、デリゲートパターンにおいて、自分自身を渡す場合や、オブザーバーとして使用する場合などです。
class SomeClass {
var observer: Observer?
init() {
self.observer = Observer(target: self)
}
}
上記のコードでは、Observer
が自身をターゲットとして使用する場面で、まずプロパティをオプショナルとして宣言し、後で初期化を行っています。これにより、初期化が前後する複雑な状態でも対応可能です。
ただし、強制アンラップを多用するとプログラムの制御が難しくなるため、必要な場面に絞ることが重要です。特に、Objective-Cからの移行時には、強制アンラップを多用してしまうケースが見受けられますが、Swiftでは慎重に使用することをお勧めします。
要点としては、デバッグ中に問題を早期発見・修正するためには、強制アンラップを適切なタイミングで使用し、プログラムの安全性を確保することが重要です。 なので、この中で例えば「エイプリオンオブジェクト」だと、ちょっとメソッドがありませんね。ないけど何か他にないかなと探しても、適切なものが見つからない場合があります。そんなときでも、ターゲットを取ろうとする際には絶対にオブザーバーがあるはずです。インスタンスができているということは、イニシャライズフェーズが終わっているわけなので。
こういったときには、強制アンラップを使っておくことで、その後は自然な流れでターゲットを参照できるようになります。こういったシチュエーションで強制アンラップのオプショナルが活用できるわけですが、調整が面倒にもなりがちです。しかし、今回はそのまま進んでいきます。
ランタイムエラーに関連する部分は、やや強めのトピックです。基本的には強制アンラップを隠蔽する方向で進めたいところです。なるほど、表示が薄れていても突き進むというのは、一部のタイトルのお話になります。ラベルが設定されていなかったとしても動いてしまったほうがいいのではという話もあるかと思います。
ここで講義を終わります。ありがとうございました。
例えば、このストーリーボードを前提として、1行目から11行目までのコードがあるとします。果たして、そこを突き進めるかどうかという問題です。表示が崩れるなら突き進むべきではという意見もありますが、仮にここで表示が崩れるような問題があるなら、そもそもストーリーボードが間違っているのではないかという考え方もあります。
強制的にここが落ちてくれたほうが、開発時に問題に気づきやすいかもしれないです。確かにそれが一理ありますが、全画面を確認しなければならないという手間が入ります。また、ストーリーボードだとなければならないという事が多いです。
自分で設定していろんな画面で使っている場合、そのミスに気づかずにリリースされてしまうとユーザーに連絡がいくことになります。それが嫌だという状況では、ストーリーボードを使うこと自体がリスクだという理由で、XIBやプログラム的にビューレイアウトを構築する方法に変わっていくのかなとも思います。
プランの前提としてバグが発生したとき、影響度を考慮して対応するべき経験が必要です。バグに誰も気づかないままリリースされてユーザーに影響が出ると、大きな問題になりかねません。標準規模のユーザーなら問題を報告するかもしれませんが、大多数のユーザーがその問題に遭遇したときのリスクも考慮するべきです。
落ちると本当に壊れるようになるため、ユーザーからの連絡は避けられないでしょう。 その制約上の問題を考えると、落ちたくないというリスクが高すぎると感じました。
確かに、その影響をできるだけ避けたいと思いますね。この発想に基づいてコメントをいただいていますし、その意図はとても良いと思います。構造上でしか防げない場合は、最初から入れていることもありますが、すべてをアイデアグレードに入れるのは難しいです。
ですが、アサートフローに2分間だけ一気に入れておくのも一つの手段です。自動的に入れられるコードのメンテナンスもありますので、バリエーションコードが増えすぎないように自動化するべきかを考えます。チェックは一定の確認をする必要がありますが、見つけても落ちないようにする、そのバランスが重要ですね。増えすぎずにメンテナンスコストを抑え、最も良いところで管理するのが鍵です。
アイデアグレードに関しては、自動で追加するのは比較的簡単かもしれません。アサーションをランタイムで使用してフルさせる技は良いですね。デバッグの際に強制的に確認するメリットもありますし、ランタイムでの表示崩れ程度で済むなら、総合的に見ても良い選択です。
他にも、自動的にできればよい部分もありますね。プロパティラッパーが利用できるのかどうか検討します。例えば、IPアドレットのバーにプロパティラッパーを適用するとどうなるでしょうか。
プロパティラッパーがコンパイルフェーズで問題なく使用できるなら、それが最良です。コンポーネントとして NSFieldComponent
的なものを使い、ラップルバリュー(wrappedValue
)を取得し、それでアサートを入れれば十分かもしれません。例えば、コンポーネントが nil でなければ return component
とし、セットはそのままで コンポーネント = newValue
にすればどうでしょうか。
アサートのオプション性についても考慮が必要ですが、オプショナルにしないといけない部分もあります。アサーションやテスト用のチェックも含め、安全性を考えた実装が求められます。コンパイルフェーズで問題を防ぐためです。
IPアウトレットについても考慮する必要がありますが、オプションの扱いが少し複雑になるかもしれません。このバランスを取りながら、コードの安全性とメンテナンス性を保つのが重要です。 これにプロパティを使って、ノーオプショナルが許可されていたら、それで大丈夫ですよ。そうすると、もし不正があれば効率で検出できるので、不正は避けられます。しかし、ページが崩れる程度ならまだしも、ランタイムエラーになると怖いですよね。
例えば、NSテキストフィールドにラップアップしないといけなくなります。本当は、ダミーのテキストを使って空のユニットで対応することも考えられますが、それだとやはり問題がありますね。空のユニット自体が不確実で怖いので、オプショナルを扱うしかありません。アンセーフにするのも一つの方法ですが、それでは意味がないのかもしれません。
例えば、オプショナルに対して !
を使ってアンラップするのも手です。オプショナルがnilのときにエラーを出すような書き方も考えられますが、それもまた怖いです。クエスチョンマークを使ってオプショナルにする方法もありますが、何も対応しないほうが安定感があります。
例えば、オプショナルとして値を返し、その値をチェックする方法もあります。NSテキストフィールドのように、複製したインスタンスを使う場面もありますし、逆に避ける場面もあります。時間になったので、この辺りの話はまた次回にしましょう。
コメントで教えてもらったように、レイジーバーを使う方法なども考えられます。初期化フェイズがいつ動くかわからない問題もありますが、ディグセットと組み合わせるなどして、最適な方法を模索するのが重要です。
具体的な回避方法やその問題点について、次回の勉強会でも深掘りしていくと面白いかもしれません。では、今日はこれぐらいにしておきます。お疲れさまでした、ありがとうございました。