今日も引き続き
強参照循環
を解消する手段のうちのひとつ、無所有参照
まわりの挙動を具体的に眺めていきます。前回に続いてこれまでにお話しした 弱参照
を気にしつつ、前回より具体的に、その特徴をおさらいしていく感じになりそうです。よろしくお願いしますね。———————————————————————————
熊谷さんのやさしい Swift 勉強会 #237
00:00 開始
00:30 今回の展望
01:02 前回のコードのおさらい
02:14 前回のコードの解説
02:43 イニシャライザーで整合性を確約していく
06:03 特定の顧客への参照を保持する変数を用意
07:50 解放できるようにオプショナル型を使う
09:28 ここまでのコードの整理
10:05 ここでの強制アンラップに特段の意味はない
11:14 無所有参照を使った参照関係
14:04 参照保持のされ方を具体的に見てみる
17:59 強参照が解放される様子
18:58 ARC による循環参照の解消
20:23 同時か否かはどこまで細かく見るかで違う
21:37 循環参照が解消される仕組み
24:58 安全性保護の観点から見た生存期間
26:57 検査をしない無所有参照
29:18 移譲先は unowned で定義することも
30:35 メソッドの呼出規約
31:56 クロージング
———————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #237
では、続けていきましょう。無所有参照について見ていきます。ここに時間をかけてきましたが、今日も引き続き具体的なところを見ていきます。
スライドでいろいろ説明してきましたが、毎回復習ばかりしていると進まないので、まずはコードを見ていきましょう。わからないところがあれば遠慮なく質問してください。前に話したことでも構いませんので、気にせず質問してください。
さて、「クレジットカードは必ずクレジットカードに結びついている」という考え方についてです。クレジットカードはカスタマーと結びつくことがあり、一方がオプショナルでもう一方はそうではない、という関係があります。しかし、これだと循環参照が起こる可能性があります。そのため、カスタマーとクレジットカードの生存期間を考慮します。現在想定している仕様では、クレジットカードのほうがカスタマーより生存期間が短いことが確約されています。この仕様に基づいてコードが作成されています。
次に、前回、イニシャライザーの適切な利用によって顧客とカードの関連付けを保証することの重要性について話しました。広い視点で見ることによって、インスタンスが必ず適切な値を持つことを約束するのがイニシャライザーの役割です。
イニシャライザーの話に触れましたが、これは大事なことです。特に無所有参照の形成時における生存範囲の長さ(ライフタイム)を確約する際に役立ちます。イニシャライザーに関する知識は他の部分にも重要ですし、しっかり勉強すると視野が広がります。
クラスのイニシャライザーに加え、構造体のイニシャライザーも学ぶことで、広範な知識が得られます。デフォルトイニシャライザーやコンビニエンスイニシャライザー、リクワイヤードイニシャライザーなども重要です。これらについてはこの勉強会でも何度か触れているので、興味がある方は過去の動画を参照すると良いでしょう。
続いてコードの具体例です。例えば、ある顧客の参照を保持するために、
ジョン
という名前のオプショナルなカスタマー変数を定義している場面です。これはわかりやすい例ですが、変数名 ジョン
は文脈によってはもう少し汎用的な名前が良いかもしれません。var ジョン: Customer?
などと定義されることがありますが、通常はもう少し汎用的な名前、例えば
customer
とかが良いでしょう。ここまでで次に進んでみましょう。顧客との関係性を持つコードや、その関連付けについての例が次に出てくると思います。具体的なコードに触れつつ、理解を深めていきましょう。 とりあえず、ジョンさんを入れるための変数を作っているわけですが、ここで特定の顧客への参照という話が出てきます。具体的には、特定の顧客インスタンスであるカスタマーネーム「ジョン・アプルシード」さんですね。この人が特定の顧客インスタンスとなり、そのインスタンスへの参照を代入するための変数を作成しています。複雑に見えるかもしれませんが、実際には難しいことは書いていません。
さらに、この変数はオプショナルなので、初期値として
nil
を持つことで、このインスタンスを解放できるようにしています。ウィーク参照のときと同じ考え方で、オプショナルにすることで nil
を代入できるようにし、インスタンスを解放できるようにしているわけです。この時点では nil
が代入されていて、後で初期化する予定だということだけ理解していれば十分です。また、オプショナルの特別な特徴についても説明しています。たとえば
john!
のように強制アンラップする必要がある場合もありますが、これは今回のコードが特殊な例だからです。実務ではこのような書き方をする機会はほとんどありませんが、今回は特定の例を理解するためにこの書き方を使っています。具体的なコードについて言えば、「ジョンさん」という変数がオプショナルで定義されていて、そこに対してインスタンス化したものを代入し、そのジョンさんのカードに対してクレジットカードを代入するという流れになっています。
ここで重要なのは、ジョンさんのインスタンスはヒープ領域に確保され、そのアドレスをジョン変数が持っているということです。これはクラス型の特徴であり、構造体の場合は異なる動作をします。構造体の場合は直接変数に値を持ちますが、クラスの場合はヒープ領域にアロケートされ、そのアドレスを参照する形になります。
具体的には、クレジットカードのインスタンスがジョンに関連付けられ、共参照が形成されます。クレジットカードのインスタンスも無所有参照を使ってカスタマーに関連付けられています。このような設計により、クレジットカードはジョン経由でのみアクセスできるという制約が設けられています。
今回はここの理解を深めるためにオプショナルを用いているので、普段のコードではやや特殊なケースとして見てもらえればいいかと思います。 先ほどのコードに戻りますが、このコードだけを見ると、確かにそういうコードになっているなと感じるところがありました。自分では意識し損ねていた部分があって、面白かったです。クレジットカードのインスタンスを変数で保持するやり方もありますが、確かにこの方法も悪くないと思いました。それで全然OKですね。
このコードにおいて、
Customer
クラスがあります。クレジットカードの名前くらいは持たせておきますか。それでは初期化(イニシャライザー)の部分に進みます。クラスの場合は、初期化処理を自分で書くか、リファクター機能で生成しなければなりません。このあたりは長くなるので今回はこれぐらいにしておきます。基本的に
Customer
と CreditCard
があって、例えば以下のようにコードを書きます:let customer = Customer(name: "John Appleseed")
let creditCard = CreditCard()
customer.card = creditCard
こういった書き方も悪くありませんが、コードを見やすくするために工夫することも大切です。例えば、メンバーとして
Customer
クラスにクレジットカードを持たせることが考えられます。ポイントとして、
Customer
から CreditCard
を所有することにより、相互参照ができないように設定してあります。これにより、Customer
と CreditCard
の間で弱参照(weak reference)が形成されます。ARC(Automatic Reference Counting)の役割として、相互参照が解消されたタイミングで、そのインスタンスを自動で解放してくれます。例えば、
John Appleseed
のインスタンスがどこからも参照されなくなったタイミングで、それが解放されます。すると、CreditCard
も解放されるので、結果的にどちらもメモリから解放されることになります。このようにして、メモリリークを防ぐための仕組みが機能するわけです。ARCがどのようにインスタンスを解放するのかについて考えると、確かにこの段階で
Customer
と CreditCard
のどちらも生存期間が終わり、メモリから解放されています。具体的には、一方が解放されると他方も同時に解放されます。少し気になるかもしれませんが、このような生存期間の管理はARCが適切に処理してくれます。とりあえず、このような流れで理解していれば問題ないと思います。 こういった点が結構大事になってくるところがあります。なぜ大事になったことがあったかな、どこまで掘り下げるかで少し違ってくることがあるんですよね。変数周りの話だった気がしますが、解釈が違うと意味が変わってくるんです。確かポインターの話だったかな。忘れちゃいました。でも大事なのは、どこまで見るかによって違ってくるけど、これがいらなくなったら「じゃあこれもいらないね」くらいの感覚かな。じゃあ一緒に捨てちゃおうという感じです。
共参照が動いてくれて、順番参照が起こらない、共参照循環という言葉のほうが好きかな。共参照循環が起こらずにうまく解放できたね、という例ですね。
次の話です。カスタマーインスタンスへの参照がなくなったから、それが解放されます。その後のクレジットカードインスタンスへの共参照もなくなるため、これも解放されます。つまり、ジョンさんに入れられるように作っておいたけど、実際に入れると共参照が解消されて、その先のクレジットカードもろとも解放されるというコードになっています。
実際にこのコードを実行すると、イニシャライザーも仕掛けておいてプリントされるようになっているので、両方解放されることがわかります。そういった話ですね。難しいところはないけど、微妙なところで引っかかるな。ここなんか、いい感じに折り合いをつけられる国語力のある方いますか?
「そうした後に解放されると、ライフタイムがクレジットカードのほうが寸分長い」という説明がよくわからないかもしれませんが、気にしないでいいです。普通はそうします。今まで散々ライフタイムがクレジットカードのほうが長いと説明していたけど、もう一回戻っていきましょう。暗黙のうちに、カスタマーのほうが短いと言ってましたね。
カスタマーのほうが長いから、暗黙のついているその順番が崩れた感じがするけど、もうごくわずかにカスタマーのほうが生存範囲が長いのを気にしていないのかもしれません。普通は気にしないなら、それでよしとしておきましょう。
次へ進みます。安全ではない無償参照の話です。先ほど紹介した例では、無償参照を安全に使う方法を示していますが、微妙な時間差の話ですね。安全の観点から言うと、ほんのわずかに長生きしている場合でも、外部のプログラマーがカスタマーにクレジットカード経由でアクセスすることはARCのレベルでは起こり得ません。平行処理すると話は別ですが、データレースが起こる可能性があります。ただ、ARCの間ではクレジットカードからカスタマーをたどることはないので、普通は気にしないでいいでしょう。
だから、ライフタイムがどうというよりも、その前提が崩れない限り問題ありません。実行性能と安全性のために、無償参照も提供されています。自分でこのスライドを作っておいたんだけど、確かに
Unowned
と Unsafe
がありましたね。これを使っている人はいますか? 記憶の彼方に覚えていた人もいるかもしれませんが、そういえば「アンオウン(unknown)」と「アンセーフ(unsafe)」という表記がSwiftにあります。まず、そのまま実行してみましょう。動いていますね。これが「アンセーフ」な書き方です。実行時に間違いが発生することは間違いありませんが、これは「アサート」でチェックしているのか、未定期動作だから落ちるのかといった違いもあります。他に何か同じようなものがあったでしょうか。「アンオウン」のほかに「ウィッグ(weak)」?いや、ここには「アンオウン」か「アンセーフ」しかないですね。
「アンオウン」と「アンセーフ」を見たことがある方はいますか?「アンオウン」を使う場面をよく見かけるんですが、「アンセーフ」を使う場面もありましたかね。せっかくなので、検索してみましょう。どこで見たんだったかな。この話は興味深いですね。
なぜデリゲートは「アンオウン」や「アンセーフ」プラス「インプリシティ(implicitly)」と「アンラップル(unwrappable)」なのでしょうか。ちょっと違いませんか?これはツイートの話題から来たのでしょうか。もしかしたら、言語に関する問題かもしれません。
デリゲートは通常「オプショナル(optional)」か「ウィッグ(weak)」を使います。この疑問は自分の宿題として、面白い情報があったらまた紹介したいと思います。
weak
を使うべきで、「swift
プラスoptional
」というタイトルも違うかもしれませんね。単純な間違いとは思えないので、考えてみましょう。ただ、少し短い文ですが、これもちゃんと読んでおきましょう。「アンセーフ」で始まるメソッドも確かに見ますし、それと同じように、呼び出しの際の注意点や、クロージャー(closures)なども出てくると思います。メソッドの呼び出し契約を作ること、「コンベンション(convention)」が重要ですね。確かに、これはとても良い話です。
convention
ブロックや呼び出しインターフェース、インターフェースを決定する方法など、このような「コンベンション」がありましたよね。呼び出しのconvention
を作る際にも選択がありますが、これは少し余談ですし、時間もなくなってきたのでこれくらいにしておきましょう。とにかく、「アンセーフ」という選択肢もあることがわかり、面白いです。使いどころを考える必要はありますが、パフォーマンスがクリティカルでなければ使わないほうが無難かもしれません。「アンオウン」だけで全然問題ない場面も多いです。
時間になってしまったので、今回はここまでにしましょう。お疲れさまでした。ありがとうございました。


