今回も引き続き 強参照循環
を打開するための手段のうちの 弱参照
についてを見ていきますけれど、今回はその具体的な様子のあたりを眺める回になりそうです。どうぞよろしくお願いしますね。
————————————————————————— 熊谷さんのやさしい Swift 勉強会 #229
00:00 開始 00:49 これまでに紹介した弱参照の例 02:35 weak を使うかは生存期間で判断する 03:09 見方によって生存期間は変わってくる 06:45 相互参照を形成させる 09:16 強参照が解消される様子 10:49 弱参照が解放されるまでに関与する仕組み 12:57 コードの自動生成に関する雑談 14:16 単一の強参照が解放される流れのおさらい 16:23 ガベージコレクションと弱参照 27:01 ほかの言語にみる弱参照 28:03 条件付き弱参照というのもあるらしい 29:06 クロージングと次回の展望 —————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #229
はい、では始めていきましょう。今日は「ウィーク参照」について学びます。前回のスライドでは、「ARC(Automatic Reference Counting)」によって自動的にメモリ管理が行われるという話をしました。その中で、プレイグラウンドでの実際のコード例を見る形で展開が終わりました。今回は具体的な例を見ていくことになります。
まず例を見ていきます。パーソン(Person
)とアパートメント(Apartment
)というクラスがあって、パーソンがアパートメントへの参照を持ち、アパートメントがパーソンへの参照を持つという状況を考えます。これらの参照にウィークを使わなかった場合、循環参照が起こり、メモリリークやデイニシャライザーが正しく動作しないといった不具合が発生します。そういった問題を回避するためにウィーク参照を使用します。
具体例を見ていきます。次の例では、Person
とApartment
というクラスに関して話されています。今回の例では、Apartment
のtenant
プロパティを弱参照(ウィーク参照)にしています。
class Person {
var apartment: Apartment?
}
class Apartment {
weak var tenant: Person?
}
このようにtenant
のほうを弱参照にしています。これは、アパートメントよりパーソンが先に解放される可能性があるからです。ウィーク参照にすることで、パーソンが解放された際、アパートメントが保持する参照も無効となります。このように、参照の生存期間を考慮しながら設計するのが重要です。
この例だと、パーソンがアパートメントに住んでいるというモデルになっていますが、契約管理などを目的にしている場合、参照がnil
になることは問題となることもあります。例えば、パーソンがリリースされたら、その参照がnil
になり、誰がそのアパートメントに住んでいたか分からなくなるというケースが考えられます。
一方で、アパートメントが先に解放される場合、契約情報が失われるのも問題です。このように、どちらをウィーク参照にするかは設計の問題となります。
さて、具体的なコード例を使って、この参照関係を作ってみます。
var john: Person? = Person()
var unit4A: Apartment? = Apartment()
john!.apartment = unit4A
unit4A!.tenant = john
ここで、john
さんとunit4A
というインスタンスを作成し、それぞれ相互に参照を持つようにします。この場合、john
が解放されると、unit4A
のtenant
プロパティは自動的にnil
になります。これにより、循環参照が発生せず、メモリリークを防ぐことができます。
この設定により、パーソンのインスタンスが解放された際、自動的にメモリが解放され、プログラムの制御面でのミスを防ぐことができます。
以上が今回の大事なポイントでした。具体的なコードを通して、ウィーク参照の使い方とその意義を理解できたのではないかと思います。 今回は、アパートメントのテナントがウィークプロパティになっている部分を検討します。ここにパーソンインスタンスの「ジョンさん」を入れています。このとき、「ジョンさん」への参照は弱参照になります。前のスライドに戻って確認してみましょう。このコード自体には何も変数はありませんが、アパートメントの型の設計により、アパートメントのテナントとして「ジョンさん」が弱参照になるという話です。
次のスライドでは、ジョン変数に「二郎」をセットし、それが持っている強参照を解除するという状況を見ていきます。このインスタンスはイニシャライズされてヒープメモリ上に置かれますが、ストロング参照がなくなると、ヒープ参照だけが残ります。これにより、「二郎」を入れたときにパーソンインスタンス「ジョン・アップルシード」への弱参照がなくなるということです。
つまり、ジョンに「二郎」を代入すると、このインスタンスに対する弱参照が解除されます。ジョンにnil
が代入されることで、パーソンインスタンスへの参照が切れます。ユニット4Aのテナントは弱参照のため、強参照がなくなるとパーソンインスタンスが自動的に解放されます。このとき、アパートメントのテナントにも自動でnil
が代入されます。
完全にパーソンインスタンスが参照されなくなると、ARC(Automatic Reference Counting)のシステムが面倒を見てくれて、パーソンインスタンスを解放してくれるという動きになります。パーソンインスタンスへの強参照がなくなると、それが解放され、テナントプロパティにはnil
が設定されるのは、ARCのおかげです。
イニシャライザーは参照カウントがなくなったときにリファレンスカウンティングがやることになります。オートマチックリファレンスカウンティングは参照カウントを管理し、テナントプロパティにnil
を入れてくれる仕組みです。同じ弱参照でも、オートマチックリファレンスカウンティングは参照カウントを支援するのが主な役割です。
今流行りのAIがもっと発展したら、AIがコードを自動で埋め込むようになるかもしれません。現状ではコンパイラーが自動生成コードを入れるのが不安な部分もありますが、それが当たり前になる時代が来るかもしれません。
次に進むと、ユニット4Aを解放する話になります。今アパートメントインスタンスへの参照がユニット4A変数に残っている状況です。この参照を解除すると、アパートメントインスタンスへの参照がなくなり、自動的に解放されるという流れになります。ユニット4Aに「二郎」を代入することで、ユニット4A変数からの強参照がなくなり、アパートメントインスタンスが参照されなくなります。 なので、これはリファレンスカウンティングの仕組みによって自動的にデイニシャライザが呼び出されますよ、という感じです。前に戻ると、唯一残っていたユニット4aの強参照をユニット4a変数に代入することによって、この参照を削除し、アパートメントインスタンスが宙に浮いた状態になります。それで開放されるのです。
次のスライドを見てみましょう。このような仕組みになって難しいことはないですね。ここまでの内容で何か問題はないと思います。ただし、リファレンスカウンティングのシステムによって目に見えないところに追いやられるため、プログラムに慣れてない人やARCリファレンスカウンティングに慣れてない人は場合によっては混乱するかもしれません。
次に進みます。ARCに関する弱参照のお話です。遅くガベージコレクションを使うシステムの場合、別の言語の話になりますが、強参照されていないオブジェクトはメモリーの圧迫がガベージコレクションを引き起こしたときにザビって開放されるため、弱参照は単純なキャッシュ機構を実現するために使われます。しかし、ARCを使うシステムの場合、最後の強参照が開放されると速やかに開放されるため、このような用途には弱参照が適さないのです。
この文章を読んだ時、弱参照をこのように使うというのは驚きましたが、文化の違いというものでしょうか。単純なキャッシュ機構を実現するために弱参照が使われるのです。これだけ言われても最初は何のことか全然わからなかったのですが、後から調べてみたらそういうことなんだと理解しました。弱参照でガベージコレクションがメモリー圧迫をするまで開放しないのを利用してキャッシュに使うということです。要は、弱参照を持たせておき、弱参照を切るのですね。弱参照を切った後はガベージコレクションが走るまで弱参照が温存され、それをキャッシュに使うのです。
やることはやるといえばやるかもしれません。弱参照などを使う場面で、こういったメモリーが圧迫されるまでは残しておくキャッシュ機構を作ろうと思ったらこのようになるかしれません。このときに弱参照を活用すれば安心感があるかもしれません。しかし、弱参照が完全に切れた時に、弱参照を強参照に戻すことがあるということでしょう。この点が大胆な発想だなと思いましたが、よく考えるとクロージャーのキャプチャーでも似たようなことをやっていますね。
例えば、以下のように書けます。
class Object { }
let object = Object()
DispatchQueue.global().async {
[weak object] in
if let strongObject = object {
// strongObjectを使った処理
}
}
このコードのように、非同期で実行する際に弱参照を使うことで、オブジェクトのライフサイクル管理を柔軟に行うことができます。オプショナルバインディングを用いて、オブジェクトが解放されない限りは安全にアクセスできます。このような使い方が理解できると、実際の開発に活用できる幅が広がるでしょう。 こういうふうにコードを書いてあげれば、オブジェクトに対して動作がわかってきますよね。こういうコードを書いたとき、一瞬ここが非同期(async
)とかした、このクロージャが実際に実行順が回ってくるまでの間にオブジェクトが解放されていた場合、そのとき解放されてしまうことがあります。これだとやっぱり怖いと思います。
元の結論に戻ってきましたが、要はオブジェクトが解放される可能性があって、実行順が巡ってくるまでの間に解放されていたら、それはnil
になっているわけですから、リターンするわけです。しかし、これがガベージコレクションとウィーク(weak
)を活用してキャッシュのようなことができるならば、想像しているやり方で当てるなら、これができるのだとしたら、実行順が回ってくるまでにnil
になっていたとしても、ガベージコレクションが発動しないうちに実行順番が回ってきたら復活するということではないでしょうか。なんか怖い感じもします。
仮に、ここのオブジェクトに対して何か操作をした場合、その時点で復活すると思います。これはまずい例ですが、普通は平然とした読み方をすれば問題にはならないでしょう。しかし、ゾンビのように復活するように感じて、少し面白い文化だなと思いました。Javaだとそんなことはわざと使わないものです。
ちなみに、昔Javaを好んで使っていた時期があります。Java Applet、JDK1.2あたりの頃ですね。Java Appletはブラウザ上でネイティブアプリケーションが動くなどの記述が有名でした。今聞いても大したことないかもしれませんが、当時はすごいことでした。ダイレクトXやアクティブXと並んで、マイクロソフトのアクティブXはインターネットエクスプローラー専用のネイティブアプリケーションをブラウザを介してアクセスするものでした。
そのころにJavaを使っていたのですが、自分の技術レベルが低かっただけかもしれませんが、ガベージコレクションという言葉は知っていてもあまり意識せずにコードを書いていました。今振り返ると思い出は多いですが、メモリ管理なども全然考えていなかったと思います。C#にも弱参照(weak reference
)はありますが、あまり使われないようです。
また、弱参照に関連する面白い仕組みとして、C#でのConditionalWeakTable
というものがあります。キーが存在している間は値を保持し、キーが解放されると値も解放される仕組みです。キーが存在している間だけ値を保持するという、面白い管理の仕方です。
ですので、逆参照を使った技術もいろいろあるかもしれませんが、それが安全に使えるかどうかは確認が必要ですね。もしかすると、自分が知らないだけで安全に使える方法もあるのかもしれません。こんな感じで逆参照の話は一段落つけましたので、次回は無償有参照(unowned
)について見ていこうと思います。
今日は時間になったので、イベントはこれで終わりにしましょう。お疲れさまでした。ありがとうございました。