今日から再び本編に戻って、 強参照循環
を解消する手段のうちのもうひとつ、無所有参照
まわりの挙動を具体的に眺めていきます。これまでにお話しした 弱参照
を気にしつつ、その特徴をおさらいしていく感じになりそうです。よろしくお願いしますね。
————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #236
00:00 開始 00:11 今回の展望 00:55 無所有参照の基本のところ 01:33 使いどころの見極めは難しい 02:36 無所有参照で循環参照を回避する例 03:46 循環参照が起こらなければ、強参照で良い? 04:23 弱参照はどこかに必ず強参照が必要 08:07 デリゲートにみる所有関係 09:05 unowned で実行時エラーになるのは Playground の都合 13:07 無所有参照と参照循環の説明 13:42 無所有参照なオプショナルも可能 14:26 参照型を扱うオプショナルは参照型扱い 16:37 参照型を扱う列挙型のサイズ 25:55 ポインターとは別に2つの候補があるときの内部値 27:05 クロージングと次回の展望 —————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #236
では、勉強会を始めましょう。久しぶりの本編ですね。今回は使用有参照のところを見ていきます。中間参照の解消手立てのうち二つ目の紹介になります。英語だと「Unowned」という機能ですね。これを見ていきます。
このスライドの内容はビデオから少し戻って見ると分かりますが、まず基本を簡単に振り返ります。
有参照のように強参照ではなく、自動的にARC(Automatic Reference Counting)がnil
を入れてくれることがなくなります。ただし、強参照とも違いnil
に差し替える特別扱いがないので、オプショナル型にしなくても良いのが特徴です。メリットといえばそういったところでしょうか。ただし、実際に解放されたかどうかは触ってみないと分かりませんし、その時点で解放されていればランタイムエラーになります。大前提として必ず生存していること、それを持つインスタンスよりも長い生存期間のものに対して使わないといけません。
強参照や弱参照と比べると、使いどころが見極めにくいところがありますが、具体例としては、クレジットカードとカスタマーの関係が挙げられます。クレジットカードにはカスタマーが必ず付随しているが、カスタマーがクレジットカードを持つとは限らない。カスタマーからみれば普通の強参照として持たせますが、クレジットカード側ではカスタマーをアンオウンドとして持たせます。これは循環参照を回避するための手立てです。循環参照が起こらないのであれば、アンオウンド参照にする必要はなく、普通に強参照のカスタマーを使っても問題ありません。
他の参照型をプロパティに持つときも、所有権を気にしてプロパティを設定するのが良いでしょう。強参照にしておいた方がいい場合もありますが、ただし自分の感覚では必ずしもそうではないこともあります。なんでも弱参照にするのが良いわけではなく、別の場所で参照を持っていることが前提です。
ここで過去にやった事例を思い出します。弱参照を使うときに、例えばプラス何かがあって、アイテム型で構造体を作ることがありました。構造体の場合、参照型ではなく値型なのでそういう問題は起こりませんが、参照型で初期化したときに以下のようにすることがあります。
var object = Item() // 即席でアイテムを作成
print(object)
これ自体は大丈夫ですが、もし弱参照にしたい場合、解放されてしまうことがあります。このような時点でランタイムエラーが起こります。それは何かと言うと、デアロケーション問題です。この場合はメモリが解放されてしまったためです。 即席でインスタンス化して変数に持たせようとしても、この生存範囲というのは、そのパラメーターを評価して右辺が終了するまでです。そうすると、代入はされますが、その時点でアンオウンドになりますので、右辺が終わったときにアイテムのインスタンスが開放されてしまいます。その場合、オブジェクトは開放済みのものを保持している状態になります。
ウィークの方がわかりやすかったかもしれませんが、オプショナルも同様に利用できます。ちゃんと警告を出してくれる点は優れていますね。昔はこういうところで警告が表示されないこともありました。他の言語、例えばC++やObjective-Cではそうだったと記憶しています。その結果、リソースが開放されていることに気づくのが遅れることがありました。
現在ではやはりコンパイル時の注意が行き届いている言語の方が良いように感じます。他の箇所でアイテムを保持しているかどうかも考慮する必要がありますね。例えば、デリゲートのような構造を考えると、デリゲートは通知を受け取る側よりも生存期間が短いと仮定されることが多いです。通知を送る側の方が長い間生存している場合が多いからです。このように生存期間を考慮した設計が必要です。
話を戻しますが、このグレーになっている部分は何でしょうかね。集団されているのか、残っているだけなのか。たぶんオプショナルではなくウィークでもないですね。ランタイムエラーが起きていますが、これは deallocate
もしくは deinit
のタイミングの問題かもしれません。
プレイグラウンドで発生している可能性もあり、ターミナルアプリケーションで試してみましょう。以前、NSオブジェクトで同様のバグが発生したことがあり、プレイグラウンドだけで発生していました。ターミナルアプリケーションで試してみると、エラーは出ません。ですから、プレイグラウンド特有の問題ということもありますね。
通常のアプリケーションであればエラーは発生しないはずですが、このようにインスタンスが気づかない間に開放されているという状況を防ぐためには、右辺が開放されないように、左辺にインスタンスが残るように設計する必要があります。アイテムの方が生存期間が長くなっていることを確認することも重要です。
このような問題を防ぐためには、アンオウンドやウィークの利用を考慮しつつ、サクサクとインスタンスが開放されないようにする必要があります。これが実際に難しいことは特にないですが、注意が必要です。 ここでは、いろいろな仕様を試してカスタマーのほうをアンモウンドにして循環参照を回避するコードについて解説しています。今日のスライドはここからですね。プロパティデータを回避する例として、循環参照が発生する可能性があります。そのため、生存範囲を考慮しながら、クレジットカードが必ずカスタマーと関連付いているためプロパティをオプショナルにしない仕様になっています。ただし、クレジットカードは必ずカスタマーと関連しているため、無償有参照でもオプショナルにすることが可能です。
無償有参照でオプショナルにすることも、もちろんできます。そういう場合にはオブジェクトがアイテムを持たないこともありますが、持つときには無償有参照を使うことができます。今回の仕様ではこういった定義がされています。
以前に話題にしたことがあるかもしれませんが、興味深い点があります。オプショナル型を丁寧に書くと、ジェネリックな型でオプショナルアイテムです。このオプショナル型の定義を例えると値型ですが、無償有参照とすることで特別な扱いを受けることがあります。例えば、同じ名前で定義した場合、その段階で参照するとエラーになります。
Swiftの仕様として、オプショナル型は参照型で扱われるため、Optional
に対しても参照型を利用できるのが面白いところです。具体的には、オプショナル型が中でクラスを扱う場合、参照型として振る舞います。したがって、オプショナルに対しても参照型が適用される仕様になっています。
また、サイズや動作の違いを見ていくと、独自に定義したオプショナルのサイズは8バイトです。例えば、ヒープへのポインタが8バイトではあるため、2つのケースがあっても8バイトで済むという結果になります。これは一般的なヌルポインタや他の参照型と同様のサイズになります。
このように、Swiftのオプショナル型や参照型について、細かい点を見ていくと非常に興味深い仕様がたくさんあります。例えば、同じ参照型でもヒープの先に格納されるデータの扱いが異なったり、オプショナルによる越境型も表現できたりします。これはSwiftの強力な特徴の一つです。 とりあえず手のままだね。持ってやっててもいいけど、面倒だからいいや。
これはどうすると分かるんだろう。参照型、ドレス、ドソピ、ウィークとか青の付けられないもんね。だから、言語的にはオプショナルは買いにくい。myOptional
。そうするとネット口のオプショナルの場合、構造体として言語は認識してるけど、管理の仕方が適切そうね。扱うものがここがプラスの時、プラスの時、これでもう一個やるとどうなんだというふうになった。この時1個の参照を持ってもう1バイトでこの歴史が何かを持ってるんでしょうね、きっとね。こうするとちょっと違ってくるんだ。ここはストラクトにしても変わらないんですよね。8と9、8と8だから違いだ。これデータ持ってないんでね、合わせる必要も全然ないね。
まあいいや、とりあえずこんな感じで。わからなかったね、これ8と8だっけ?そうだね。だから、var blurredEqual
。言葉がやばくて、これでネットv = myOptional.some
のアイテムがこれで同じ8バイトでしょ。だから、let w = unsafeBitCast
、えっ、これでダメだな、きっと。v = item
に直接同じサイズだけど。これどうなのかな。print w
の2倍から復元できた。分かんないね。どういうことだろうね、これね。
つまり、これどう判定してるんだろうね、ここね。unsafeBitCast
でキャストできちゃったってことは、本当そのまま生データで持ってるわけでしょ。ね、こんなふうにしてみちゃえばいいのか。これだとさすがにランタイムエラー。そうですよね。だからちょっとこの時特別なデータの持ち方をしてるっていうことなんですね。
だから、let bothUInt
ってビットパターンで。そういうふうにしたらビルドゼロとかになるのかな。なんだこれ。ポインターに典型してないとダメですよとか。よく分かんないのが出てきたね。これビットパターンに変換するとか、ビットパターンで別にできそうだけどね。そのままunsafeBitCast
でいいか。キャストでv
をこのままUInt
にキャストという言葉が出てくるんじゃないの。でいいのか。ゼロだね。ここがこの時はゼロじゃつまらないですよね。なんかのデータが入っている。なんかよく分かんないけど、こんな感じになったみたいね。
そっか、ゼロがデータ持ってるか。そっか、ゼロの時ダメじゃない。あそこがクラスだからか。アドレスだからいいんだ。値が何であれね。アドレスで値があるかどうか判定できるから、そこがいろいろ最適化が図れるんだ。コンパイラってすごい賢いですね。この辺、どうやったらそんなふうにいい感じに最適化できるもんなんだろう。とりあえずこうやってクラスをベッキュー型が持つ場合はいい感じに最適化してくれるみたいね。ケース2.0なのこれ。ここで.n
もうすると1とかになるのかな。
いらない。これもカッコがいらない。いらなくない。いらない、ここだね。ここ平らになるの。よく分かんないね。ビットサイズ変わったってこと。こっちだよね。完成後ビットキャストが失敗するなんてことあるんだ。面白いねこれ。どういうことなんだろう。あそこかそこ、偉いのかな。アイテム型がおかしくなったからだね。そこが問題だってことだ。アドレス参照先アイテムがアドレス1に問題しちゃってるよってね。そういった感じで、そこを見に行こうとしてアクセス違反になったっていう感じだ。
じゃあこんなところでちょっと無用な話になっちゃいましたが、また引き続き次回もちろん余談省のところ、実際の参照の具合を図を引用して見ていくっていう。そんな感じでまた次回やっていきますね。今日はこれで終わりにしますね。お疲れさまでした。ありがとうございました。