今日は
強参照循環
を解消するのに使われる 無所有参照
を 暗黙アンラップされるオプショナル
と使うことについてのお話です。通常のオプショナルと使う場合と特色的にはそこまで大きく違わないものの、それが表現する意味合いが変わってくるあたりを体験できるのを期待しつつ眺めてみますね。よろしくお願いします。—————————————————————————————————
熊谷さんのやさしい Swift 勉強会 #243
00:00 開始
00:30 無所有なオプショナル参照の留意点
00:53 オプショナル型を unowned に指定可能
02:22 オプショナル型は強参照されない
07:07 列挙型もひっくるめて参照型としている
14:02 無所有参照と暗黙アンランプなオプショナルとの組み合わせ
15:08 循環参照を打開するシナリオ
18:02 どちらも nil にならない相互参照というシナリオ
19:52 無所有参照と暗黙アンラップなオプショナルを使う例
21:16 コード例の方向性
23:14 初期化中に self を渡す必要がある
—————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #243
では始めていきましょう。今日は同じセクション内の新しい箇所に入っていく感じです。その前に、前回のスライドで軽く読んだ部分をもう一度見ておこうと思います。
前回はオプショナル型の無所有参照を扱う際の留意点について確認して終わりました。標準ライブラリのオプショナル型が参照型になっているため、
weak
やunowned
キーワードを使った所有権の管理が可能である、という話でしたね。これにより、オプショナルな無所有参照が実現できているという部分が非常に重要です。このような例外があることで、言語がオプショナルを使った無所有参照を想定しているという点が大切なんです。次に、強い参照を表示する必要がない点についてです。オプショナルは値型であり、参照カウントがないため、オプショナル自体が保持する内部のアソシエイト値についても同様の扱いがされます。この辺りの説明が少しわかりにくかったようなので、具体例を使って検証してみます。
今回の検証は、Playground上では試しにくいため、コンソールプログラムを使用します。以下のコードを使ってオプショナル型の参照について確認します。
class MyClass {
}
var a: MyClass? = MyClass()
if let value = a {
print(Unmanaged.passUnretained(value).toOpaque())
}
このコードで、クラスオブジェクト
MyClass
をオプショナル型の変数a
に格納し、アンラップして参照を表示します。この際に、参照カウントやポインタの動作を確認します。試しに
nil
を代入するとどうなるかも見てみましょう。a = nil
if a == nil {
print("a is nil")
}
これで、オプショナル変数
a
が参照しているオブジェクトが正しく扱われていることを確認できます。追加で、参照カウントが正常に動作することも検証します。
class MyClass {
deinit {
print("deinit called")
}
}
var b: MyClass? = MyClass()
b = nil // ここでデイニックが呼ばれるはず
このコードでは、
b
に代入されたMyClass
インスタンスがnil
に再代入された際に、deinit
が呼び出されるかを確認します。これによって、オプショナル型の参照カウントが適切に減少し、メモリが解放されることを確認できます。今日はここまでにしておきますが、さらに深く掘り下げたい方はぜひ他の例も試してみてください。 要はヌルポインターが許容された普通のポインター型になるということですね。これでつまり、裏側ではオプショナルな参照型をひとまとめにして参照型の値として扱っていると考えて問題なさそうです。それに対してリファレンスカウンティングをしていると想像してよさそうです。論理的にどうでしょうか。もし間違っていたら教えてください。
つまり、変数
B
そのものに対して、リテインやリテインパス、アサインをしたりといったような感じですね。オプショナル型は劣化型ではあるけれど、ちゃんと参照型として操作できる特例があると捉えていい気がします。そうでないと、これが仮に値型として振る舞ってしまうと、C = B
とやったときに複製が生まれて、値が弱参照されるかどうかで変わってしまうからです。でも、C言語のメモリ上の実際の値は同じになるはずです。つまり、複製が作られているわけではなくて、参照先が同じであることを確認しているだけです。アドレスは異なるのは当たり前ですが、参照先が同じならば問題ないという判断です。
オプショナルでも同じ値が入っていたようですね。これがオプショナルとして別扱いされていると、値が違ってくるのがわかります。それで、例えば独自のオプショナルを作ったときに、これで動かすと異なる結果が出るわけです。
GitHub上のプロジェクトを使って実行すると、やはりオプショナルに対する最適化が働いていることがわかります。この辺はちょっと混乱してきたかもしれませんが、次に進みましょう。この部分はそれほど重要ではなく、裏で処理されていることを知っておけば十分です。
要するに、オプショナルに対する強い参照を保持する必要はなく、その参照を内部のアソシエイトバリューとして持っているというだけの話です。次の話題に進んでしまいましょう。 では続けて、今度は「強制アンラップされるオプショナル」についてお話しします。これまで通常のオプショナル(オプショナルのごく基本的な使い方や、その特徴)について話してきましたが、今回は強制アンラップされるオプショナルと弱参照を組み合わせたときの話です。要は、オプショナルの「?」ではなく「!」を使った場合のお話です。
何が違ってくるのかというと、基本的な性質はほぼ変わらないはずです。具体的に例を見ていきますと、以前に見た弱参照と強制アンラップの組み合わせの場合の話を続けます。前回の例では、不動産物件と入居者の関係を使っていました。これらのプロパティは、両者がそれぞれ循環参照になる可能性がありました。この問題を解決するために、弱参照を使った設計が一般的に使われます。
例えば、物件とそこに入居する人の関係です。物件は特定の人物が常に住んでいるとは限らないので、入居者プロパティがnilになる可能性があります。一方、クレジットカードと顧客の関係では、クレジットカードは発行された時点で常に特定の顧客に結びついています。このように、どちらか一方が必ず値を持つシナリオにおいて、強制アンラップを使うことが適しています。
次に、強制アンラップされるオプショナルのプロパティと弱参照の組み合わせについて見ていきます。「強制アンラップされるオプショナル」は、初期化が終わればそのプロパティがnilであってはならないシナリオを対象とします。例えば、あるプロパティが初期化された後は常に validな値を持つことが保証されている場合には、「強制アンラップ」を活用します。
ただし、注意しなければならないのは、強制アンラップされたオプショナルは間違ってnilにアクセスするとクラッシュする可能性があるため、用途によっては慎重に使用する必要があります。一般的には、欠損が許されない関係を構築する際に使います。例えば、オブジェクトが解放された後には、どちらのプロパティもnilになることがない場合などです。
個人的には、状況に応じて考え方次第で柔軟に使い分けることが大切だと思います。全てが一概に「これが正しい」とは限らないので、設計時のシナリオに併せて最適な選択をするのがポイントです。 今回のシナリオでは、一方のプロパティを「強制アンラップ」したオプショナルプロパティとして使うのが便利だというお話ですね。循環参照を打破しつつ、一度値が決まったらnilにならないプロパティを強制アンラップのオプショナルで表現するというものです。これは自然な考え方ですね。
次に進むと、こういった状況で、両プロパティの初期化が一度終われば循環参照が発生することなく、直接ワークセットが可能になります。ここでは、そのようなプロパティの関係を設定する方法について説明します。これから新しい例が出てくるのですが、今までの二つの例に加えて、第三のシナリオを扱います。
具体的には、
Country
とCity
を定義するという話です。この段階で、クラスを使うのが適切かどうかは議論の余地がありますが、今回は循環参照の説明のためにあえてクラスを使用します。すべての国が常に首都を持ち、すべての都市は国に所属するという前提で話は進みます。
Country
クラスはcapitalCity
プロパティを持ち、City
クラスはcountry
プロパティを持つという関係です。具体的に定義すると、
Country
クラスでは以下のようになります。class Country {
let name: String
var capitalCity: City! // 強制アンラップのオプショナル
init(name: String) {
self.name = name
}
func setCapital(city: City) {
self.capitalCity = city
}
}
一度首都が設定されればnilになることはありません。この設計は一見すると微妙ですが、何か抜けているかもしれないので、とりあえずこの形になっています。
次に
City
クラスは以下のようになります。class City {
let name: String
unowned var country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
City
クラスはunowned
のプロパティでどの国に所属しているかを保持します。一度決まったら変更はできません。このようにすることで、強制アンラップを使わずに済みます。国がランタイム中に変わらない限り、このコードで問題ありません。この後は、
Country
とCity
の動作について、さらに詳しく次回見ていくことにしましょう。では今日はこれで終わりにします。お疲れ様でした、ありがとうございました。

