本日は前回に続いて Automatic Reference Counting
の最後の項、 クロージャー
と クラスインスタンス
間における 強参照循環
の話題について見ていきますね。何気なくややこしいところと思うので、いつもに倣ってゆっくりじっくり見ていけたらいいなと思ってます。よろしくお願いしますね。
—————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #253
00:00 開始 00:14 クロージャーの強参照循環は見落としがち 01:21 クロージャーでの強参照循環が発生する場面 02:47 キャプチャーとクロジーングオーバー 04:17 クロージングオーバーは強参照ではなく延命 06:28 キャプチャーリストが強参照を保持することの確認は難しそう 16:22 クロージャーが参照型であることに起因 18:44 クロージャーに見立てた構造体で観察してみる 22:18 クロージャーのユニーク性は判定できない 22:52 クロージャーのサイズは 16 バイト 24:45 C 言語互換のクロージャーは 8 バイト 26:51 クラスとクロージャーが互いを存続させる 27:41 キャプチャーリストで循環を解消できる 30:24 クロージャーキャプチャーリストって言う? 31:22 クロージング ——————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #253
参照型であるということがポイントですね。だから、多くの開発者が循環参照に気をつけなくてはいけないと気付きにくいのかもしれません。
次に理解しておくべき点は、クロージャー内で捕捉されるインスタンスがどのように動作するのかということです。クロージャーがキャプチャーリストを使っている場合、そのクロージャーはリスト内の各要素を強く参照します。これが原因で、メモリの解放が遅れるか、または解放されない(メモリリーク)ことがあります。
例えば、以下のようなコードを考えましょう:
class SomeClass {
var callback: (() -> Void)?
init() {
callback = { [weak self] in
print(self?.description ?? "nil")
}
}
}
この場合は、[weak self]
を使うことで、循環参照を避けることができます。これにより、self
が解放されるときにクロージャーも一緒に解放され、メモリリークを防ぐことができます。
また、capture list
とその使用方法についても重要です。あるインスタンスを捕捉したい場合、キャプチャーリスト内に適切に記述することで、安全に参照できます。
まとめると、クロージャーの使い方やキャプチャーの方法に万全を期すことが、メモリ管理の鍵となります。これにより、Swiftでの開発はより効率的かつ安全なものになります。
今回の勉強会では、特にこの点に注意を払いながら、後半も進めてまいります。次に移りましょう。 とりあえず値型だったりするので、参照ではなく複製という形で持つことになります。そうすると、持った先が参照型であっても大丈夫ですかね。自信がなくなってきたな。クロージャーで参照型を維持することはできるのでしょうか。例えば、オブジェクトクラスがまずあって、ストラクトとして値を持つ Value
があって、これがオブジェクトを持っているみたいな、こういう形になっている場合です。
要は参照型を値型で持つか、参照型で持つかというお話です。プラスで構造体で技術的に話せばいいのかなと実験しているところです。これで循環参照問題を解決すればいいのかな、こんな感じで。一応、こうした方がイニシャライザーを搭載するのは面倒なのでオプショナルにしようかなと思います。
Value = Value
イニシャライザーの方が綺麗だったので、例えば以下のようにします。
init(value: Value) {
self.value = value
}
これでイニシャライザーを使うと、
init(object: Object) {
self.object = object
}
とても混乱してきたので、ここでやめておこうかな。循環参照問題が起こったので、それを解決するにはどこか強制アンラップのチャンネルを作る必要があります。先日も話した内容に戻ってきますね。頭の中で整理すると、値型のインスタンスが入っていて、値は解放されちゃうから、結果として全体的にオッケーとなりますね。 このクラスだと、メモリが解放されないことがあります。その理由は、オブジェクトが参照を持っているからです。これに対処するためには、特定のバリューを消さないといけません。適切な方法で処理を行わないと、複雑な問題が発生し、それが解決されません。ただし、クロージャーは参照型なので特に注意が必要です。
クロージャーのユニーク性について検証する方法がありますが、ユニーク性のチェックは難しいことがあります。メモリレイアウトを確認すれば何かわかるかもしれません。例えば、クロージャーのサイズを取得すると16バイトになることが分かります。これは、メモリレイアウトやサイズを見てみると面白いですが、なぜ16バイトになるのかはっきりしないこともあります。場合によっては、特定の状況や設定によって変わることも考えられます。
関数は通常、プログラム領域に存在し、データ領域やヒープ領域、スタック領域にあることは少ないです。関数のメモリアドレスを参照するため、参照型としての特性が影響します。
クロージャーも参照型なので、循環参照が発生する可能性があります。クロージャーをプロパティに割り当てると、その参照はプロパティとして保持されます。結果として、クラスインスタンスとクロージャーが相互に参照し合い、循環参照が発生することがあります。
この問題に対して、クロージャーのキャプチャリストと呼ばれる機能が解決策として提供されています。キャプチャリストを使って循環参照の問題を解消する方法を学ぶことが重要です。解決策を学ぶ前に、どのようにして循環参照が発生するかを理解しておくことが大切です。
次の例では、self
を参照するクロージャーを使うときに発生する循環参照について見ていきます。このように、問題の原因を理解し、その解消方法を学ぶことが大切です。特に高級言語であるSwiftのような言語を使用すると、裏で隠れている部分の原因を見つけるのは難しいことがありますが、可能な限り努力して原因を知ることがプログラマーとして重要です。 続きのテキストをお渡し頂くか、補足情報があれば提供してください。いただいた内容に基づいて、適切に文章を整えます。 次の例では、HTMLエレメントについて見ていきます。ウェブサイトサービスのためのHTMLで、これはウェブサイトを作るときに使うHTML文書の単純なモデルを提供します。今回はAMPについて学んでいくわけですね。この例はまた次回に詳しく見ていくことにしましょう。
ところで、余談ですが、プログラミング言語の中で「クロージャーキャプチャーリスト」という表現が出てきました。一般的には「キャプチャーリスト」と言いますよね。キャプチャーリストというと、多くの人はクロージャーの中で使うものしかイメージしないと思います。クロージャーキャプチャーリストという表現は珍しいなと感じて、ちょっとメモしておきました。特に大きな話ではないですが、自分としては「キャプチャーリスト」と表現するのが一般的かなと思いますね。
今回のところはここまでにしましょう。今日はこれで時間になったので終わりにします。お疲れ様でした。ありがとうございました。