前回に続いて
強参照循環
を解消するのに使われる 無所有参照
を 暗黙アンラップされるオプショナル
と使うことについてのお話です。今回は書籍に挙げられていたサンプルコードを詳しく見ていきながら、その特徴をおさらいしていけたらいいなと思っています。よろしくお願いしますね。———————————————————————————————
熊谷さんのやさしい Swift 勉強会 #244
00:00 開始
00:21 前回のおさらい
02:23 初期化時に互いに参照しないといけない場面
04:40 GitHub Copilot が補足を添えてくれた
05:31 まだ設定できないことを知らせる方法
08:19 再代入を禁止するかは状況次第
09:12 暗黙アンラップされるオプショナルの扱い方
11:12 処理タイミングを把握・制御できることが大切
14:11 イニシャライザーは最強の初期化手段
15:25 初期化のしかたの考え方
19:16 次回の展望とクロージング
———————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #244
Swiftの勉強会を始めていきます。今日は、「強制アンラップされたオプショナル」と「暗黙的にアンラップされるオプショナル」の二つを組み合わせて使うお話の続きです。前回の内容に引き続き、今回も詳細を見ていきますね。
まず、Swiftにおける強制アンラップオプショナルについて説明します。以下のように定義されている「カントリー」という型があります:
class Country {
var name: String
var capitalCity: City!
}
ここで使われている「!」は強制アンラップオプショナルです。これを「?」と書くと、
capitalCity
は nil になる可能性があることを示します。しかし「!」を使うことで、必ず値が入っていることを示すことができます。では、これがなぜ重要なのか。例えば、
Country
クラスのイニシャライザで首都となる City
を受け取ってプロパティを初期化する場合、以下のように書けます:init(name: String, capitalCity: String) {
self.name = name
self.capitalCity = City(name: capitalCity, country: self)
}
このコードでは、
City
の初期化時に self
を渡す必要がありますが、Swift
の言語仕様では、初期化が完了する前の self
参照は許されていません。これが問題で、以下のような初期化コードを書くことができません:class City {
var name: String
var country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
ここで、
City
のインスタンスを作成するためには、コンストラクタ内で Country
のインスタンスにアクセスする必要があります。しかし、Country
のインスタンスはまだ完全に初期化されていないため、このままではコンパイルエラーになります。この問題を回避するために、capitalCity
を強制アンラップオプショナル(!
)として宣言することで、仮にnilでスタートしても、後で必ず値が設定されることを保証するのです。具体的な例として、以下のようにイニシャライズフェーズで対処します:
class Country {
var name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
var name: String
var country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
上記のようにすることで、
init
内で self
を参照するための設定が完了します。強制アンラップを用いることで、初期化されるまでにnilである可能性を許しつつ、最終的に必ず値が設定されることを保証できます。これが、強制アンラップオプショナルの意義です。 とりあえず、nil
を代入しておいてイニシャライズフェイズを終わらせるのですが、その後にちゃんと適切な値を設定します。これでイニシャライザーが終わることで、インスタンスが生成されるまでには必ずその値が参照できるようになっていることが保証されます。だから常に安全に使うことができるということが保証されているのです。ただ、ここには少し抜けがあります。その抜けについては前々回お話しした内容です。これをプライベートセットで保護しておかないと、何かの間違いでその値が
nil
になってしまう危険性が残っているので、それは避けたいところです。この場合、多分書き換えないことがほとんどですが、プログラムの仕様により書き換える可能性もあるので、注意が必要です。場合によってはこのプライベートセットにするのは適切ではないかもしれません。その場合、変数をvar
にしておく他ありません。また、
!
(ビックリマーク)で定義されたプロパティの場合、誰かがnil
を代入する時点で、その代入した人がそのnil
が絶対に入らないと考えていることが前提となります。ここはプログラマーのレベルが問われる部分ですが、!
付きのオプショナルにnil
を入れるのは基本的にタブーです。よほど特別な状況でなければ問題なく動作するはずです。この考えに基づいて、nil
を入れないようにしましょう。通常、暗黙的にアンラップされるオプショナルは
nil
を入れるべきではないとされています。標準で推奨されている暗黙のルールとして、nil
を入れるべきではないのです。本当にやむを得ない場合だけnil
を入れるようにしてください。IBOutlet
はなぜかオプショナルで、普通のオプショナルを使うのですが、あれも!
でも良かったかもしれません。とにかく、表向きに扱えるようになるまで、nil
が入っていることを意識的に使うことが大事です。今回はイニシャライズケースで収まりましたが、そうでなければ、フレームワークが対応しているawakeFromNib
のようなメソッドで初期化する必要があります。nib
やストーリーボードのようなものから読み込む際には、その制御をしっかりと行うのがプログラマーの醍醐味でしょう。これが法律的に問題ないならよいのですが、今後詳しく話題にするかもしれません。また、クロージャの順序参照の話が出てくるときに少し怖い話もできるかもしれません。
!
を使うときには注意が必要です。インスタンスの初期化が完了するまでには、必ず初期化が行われるべきです。これはフレームワークによっても保護されている部分ですし、言語仕様によっても保護されています。イニシャライザーが終わった時点で初期化が完了しています。これが言語仕様に基づく最強の保証です。 とても良いやり方として、「イニット」って書いたやつがあります。他にも「コンビニエンス」や「リクワイアード」なイニシャライザーもありますが、何も書いてないイニシャライザーのことを「デフォルトイニシャライザー(指定されたイニシャライザー)」と言います。このイニシャライザーの大事な役目は、インスタンスの保存型プロパティを確実に全て初期化することです。これはセキュリティを保護するためにも重要です。この中でビックリマーク付きの値を初期化できていると、とても理想的な書き方です。ただし、あまり闇雲に使うと、コードが複雑になりがちです。NILが発生する可能性がある場合、それを避けるためにまずはイニシャライザーの中で完結できないかを考えてください。
次に、ライフサイクルについても考慮することをお勧めします。使っているフレームワークや作成したアプリの中で、必ず初期化の過程を通過する部分があるなら、そこまでで初期化を完結させる方が良いです。
それ以上に広範な範囲でイニシャライザーを使用しなければならない場合、まずはビックリマーク付きのオプショナルが本当に必要かを考えてみてください。状況によって異なるかもしれませんが、大まかな順序としては以下のようになります。
まずオプショナルを使う必要がないかどうか検討すること。無駄なオプショナルは避けるべきです。
初期化が遅れる理由でオプショナルを使うのであれば、ビックリマークを使う場合がある。
初期化の話としてまずはイニシャライザーを考え、その次にライフサイクル。その上でビックリマークが本当に必要かを疑う。
それでも必要ならレイジー(遅延初期化)を検討する。
他にもいろいろなアプローチがあるかもしれませんが、それぞれの状況に応じて最適な方法を選びましょう。また、ビックリマークではなく「ハテナ」(オプショナル)を使うことも大事な選択肢です。説明を簡単にするためにビックリマークを使ったとしても、それが最善策かどうかは慎重に考えるべきです。
次に、具体的なコードを見て少し考えてみましょう。例えば、キャピタルネーム(首都名)を初期化する部分がありますね。これを先に設定してすべてのシティをアイデアとして作ってから、カントリーをその後に作成しようとすると使いにくくなります。東京を首都とする日本の都市一覧を作るとき、東京が重複しないようにするためには結構な工夫が必要です。時間がなくなってきたので、今日はこれくらいにしましょう。
今回は少し話がそれましたが、Swiftにおけるクラスの使用や、その違いについても次回また見ていこうと思います。お疲れさまでした。ありがとうございました。


