第253回
を最後にひたすら脱線を繰り返していた会ですけれども、今回からは再び本編に戻って 強参照循環
の中でもとりわけ クロージャー
まわりのそれについてを眺めていきます。これについても既に少し読み進めてはいますけれど、5ヶ月近くも前の話に立ち戻る都合、改めて最初のところから見ていく感じにしてみますね。よろしくお願いします。————————————————————————————————————
熊谷さんのやさしい Swift 勉強会 #308
00:00 Start
00:15 今回の展望
01:05 所有権と参照渡しは無関係と思うのが良さそう
04:21 クロージャーにおける強参照循環
05:11 強参照循環をなるべく起こさないようにする傾向
05:47 unowned にすべきかの見極めも重要
06:51 強参照にする選択肢も大切
08:20 互いに参照したときの解決手段
09:04 強参照循環はクロージャーにも見られる
09:58 強参照循環を不必要に解消していたり
12:51 解放を許すべき場面か、待つべき場面か
13:36 Task で見られがちな不要な参照管理
15:17 Task における参照管理の効果的な例
18:36 クロージャーをプロパティーに割り当てているかが争点
22:17 クロージャーは参照型
23:53 クロージャーが参照型であることは気にしなくて良いかもしれない
24:30 クロージャーで強参照循環が見えにくくなる
28:23 クロージャーが強参照循環を起こす理由まとめ
29:03 キャプチャーリストという優雅な解決策
29:50 余談:以前の話もそこそこ覚えている印象
30:41 次回以降で、クロージャーが強参照循環を形成する様子を見ていく
31:56 クロージング
32:11 余談:HTML 5 廃止?
————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #308
今回は久しぶりにスライドを使ってお話をします。最近は「Automatic Reference Counting(ARC)」について長く学んできました。今回はその最後のセクションとして、クロージャにおける共参照循環について見ていこうと思います。
5ヶ月前にもこのセクションに触れましたが、そのときに途中までしか見ておらず、だいぶ時間が経っているので忘れている部分もあるかもしれません。また、覚えていたとしても時間が経てば見え方が変わることもあります。そういったことも含めて、改めてこのテーマを取り上げることにしました。
さて、少し余談ですが、最近は脱線してSwift 5.9の新機能について学んでいました。それと並行して「オーナーシップ」についても考えていましたが、オーナーシップと参照型の話が頭の中で絡み合っていました。特に、値型と参照型という概念があり、値型は複製されますが、参照型は参照が維持されるという違いがあります。この概念に「オーナーシップ」が加わると、混乱してしまうこともありました。例えば、値型と参照型の違いと、オーナーシップが関係あるように思ってしまったのですが、それは別々に考えるほうが理解しやすいと思います。オートマチックリファレンスカウンティングとオーナーシップも別の概念です。
さらに、ARCには参照の扱い方を指定する方法として
strong
、weak
、unowned
というキーワードがあります。特にunowned
は所有権とは関係ありません。こうしたキーワードの使い方については慎重に考える必要があります。strong
、weak
、unowned
のいずれを使うべきかを理解することで、ARCに関する知識がしっかりと身につくと思います。クロージャは参照型であるため、その中で値をクロージングオーバーして保持するため、共参照循環を生じさせることがあります。これを防ぐために、特に
self
をキャプチャーする際には注意が必要です。よく使われる方法としては、weak self
やunowned self
を使うことが多いです。実践的な場面ではweak self
を使うことが推奨されることが多いですが、勉強の際にはunowned
を試してみるのも良いかもしれません。このように、ARCやオーナーシップに関してしっかりと理解を進めていくことが重要です。スライドを使った今回の学びが参考になればと思います。 久しぶりなので少し補足しておきましょう。この文章は、アプリが公式に出している「Swift プログラミング言語」の英語版を日本語に翻訳して作成したものです。それでは始めましょう。これまでは、5ヶ月前の話になりますが、2つのクラスインスタンスについて、プロパティで発生する循環参照がどのように起こり、それをどう解消するかを見てきました。2つのクラスインスタンスがお互いに所有してしまうと循環参照が発生するため、それを解消するために、参照の方法を逆参照や無所有参照にする必要があります。これはよくある話ですね。
もっと複雑な場合にも、循環参照がクロージャー内で発生する可能性がありますが、そのような状況はあまり頻繁には起きません。どちらかというと、クロージャーはコンプリートハンドラーなどで使われ、通常はストロング参照が問題ありません。しかし、そうでない場合には、中で保持する必要があるときがあり、そのようなケースは意外と少ないのではないでしょうか。
クロージャーを使った循環参照を回避する場面で、よくあるのが、必要ないにもかかわらず解消している場面です。どちらかというとこうした場面の方が多いのではないかと思います。例えば、以前はあまりないかもしれませんが、ディスパッチキューに投げる際にプレディケートとしてクロージャーを使う場合などです。その中で自分自身の何かを触る場合、
self
を使うわけですが、このとき、ウィーク参照でself
を回避することが必要ない場合もあります。具体的には、クロージャーがコンプリートハンドラーとして使用される際に、ウィーク参照にして
guard let self = self else { return }
のように書くことがありますが、これは必ずしも必要でないことが多いです。もちろん状況によりますが、この方法が安全ではないという場合も必ずしもそうではありません。むしろ、解放のタイミングを考慮することが重要です。コンプリートハンドラーが呼ばれる前に親が解放されるかどうかが大切で、本当にその状態でいいのか、コンプリートハンドラーが処理するまで待つべきかどうかを確認することが大事ですね。 待たなければならない場合には、その待機が不必要ですが、待つべきでないときには、親が解放されたときに何をするかが重要だという考え方があります。これは循環作業とは別の話です。基本的には循環作業は起こらないと理解してください。いただいたコメントでは、「コンパレーション」というよりは「タスク」に関する内容ですね。タスクが実行されるときに親がいない可能性があるかどうかが重要です。ここでいう親とは、私たちのインスタンスを指しています。
もし「安全だから」と教えてもらっておまじない的に処理をしている方がいらっしゃるなら、循環参照やライフサイクルについて考えてみると良いでしょう。それによって、これらの周りがクリアになってくると思います。時間があれば、自分なりに考えてみてください。
具体的には、タスク内で
weak self
を使ってawait something
する際に、guard let self else { return }
という形が有用です。アウェイトは中断ポイントとされていて、このポイントで何かが起こります。したがって、タスクが進行したときにセルフは存在しているものの、このアウェイトを通過した後にセルフが存在しているとは限りません。タスクにとってはセルフかどうかは関係なく、単にインスタンスを持っているだけです。しかし、それが解放される可能性があります。重要なのは、ここで
weak self
をしっかりと持ち続けているのは問題になるという点が書かれています。この点についても、しっかりと考えておくといいですね。 ウィークセルフ(weak self)というのは、非同期処理などのコンテキストで通常の強参照を避けるためによく使われるテクニックです。たとえば、Swiftの非同期処理でawait
を使用する場面で、ウィークセルフを用いることがよくあります。しかし、その際には注意点があります。まず、ウィークセルフを用いることで、非同期タスク内で
self
が解放された可能性を考慮しなければなりません。このため、guard let self = self else { return }
のような書き方をすることで、self
が存在しているかを確認することが一般的です。それにより、self
が解放されていた場合には安全に早期リターンできます。また、ウィークセルフの使用を判断する際には、強参照のループ(retain cycle)の発生を考慮するだけでなく、その非同期タスクがどのように保持されるのかも重要です。タスクやクロージャーがどこかのプロパティに保持され、その中で
self
にアクセスすると強参照ループが発生する可能性があります。非同期タスクでは、メモリ管理を明示的に考慮し、self
がキャプチャされる状況で問題が生じないようにすることが求められます。したがって、このような技術を用いる場合には、強参照から解放されているかどうかの状況を理解し、そのインスタンスが持つプロパティやメソッドにアクセスできるようにすることが重要です。
クロージャー内でインスタンスにアクセスする場合、クロージャーが後で実行されるため、その時点で
self
をキャプチャ(保持)している可能性があります。これが原因で強参照の問題が起こり得るのです。この話題では、クロージャーをプロパティとして保持する際に注意すべき点が強調されていました。メモリ管理はソフトウェア設計における大切な要素の一つですから、この点に十分注意を払う必要があります。 クロージャーのキャプチャーに関して、広範な意味でのキャプチャーが起き、参照循環が形成されることがあります。クロージャーが参照型であることが原因で、その影響を理解することが大切です。しかし、考えすぎると混乱することがありますので、あまり深く気にする必要はありません。クロージャーをプロパティに割り当てると、そのプロパティに参照を割り当てたことになります。これは、クラス同士の循環参照と似たような形で、クラスとクロージャーの間で参照循環が起こる可能性があるということです。具体的には、クラスAがクラスBを持ち、クラスBがクラスCを持つという形で、クロージャーが参照循環に介入することがあります。
クロージャーが挟まれることにより、参照循環の検出が難しくなることがあります。通常のクラス間の参照循環では、どのようにインスタンスが保持されているかが比較的わかりやすいです。しかし、クロージャーによって間接的に保持されると、どのタイミングで参照が切れるのかがわかりにくくなります。
例えば、クラスAがクラスBを持ち、クラスBがクラスCを持つとします。このときにクロージャーが関与すると、クラス間の参照が直接的ではなくなるため、どこで解放されるかを管理するのが難しくなります。これにより、クロージャーを含む場合は参照解放が適切に行われないこともあり得ます。
このような複雑な状態はクラスとクロージャーが絡むことで発生するため、頭が混乱しそうな話になります。ただし、そこまで深く考えて混乱するようであれば、まずは基本的な概念から理解を深めていくことが重要です。必要があれば、具体的なコードを書いて動作を確認し、実際にどのような参照の動きがあるかを調べていくと、徐々に理解が進むでしょう。 順番参照が起こる可能性についての理解は、クロージャーがインスタンスを保持しているためだと考えると十分です。クロージャーが変数をキャプチャすると、それがクロージャー内で保持されてしまいます。この状況は、プログラムの順序に影響を与える可能性があります。特に注意すべきは、クロージャー内部でキャプチャされたインスタンスが、予期しないタイミングで変更されることです。これは重要なポイントです。しかし、すべての順番参照の問題が、キャプチャの順番に影響されるわけではないことも覚えておく必要があります。この違いをきちんと理解することが大切です。
さて、キャプチャリストに関してですが、クロージャーのキャプチャリストは、順番参照の問題を解決するための有効な手段として提供されています。キャプチャリストについて学ぶ前に、どのように順番の問題が発生するかを理解することが重要です。
記憶が定かでなくても問題ありません。人それぞれに異なる覚え方がありますし、何度も復習をすることで新しい発見があります。自身の成長を感じながら、学んでいくことができると良いですね。
順番参照がどのように発生するのか、どのように取り扱うのかを学びます。この段階では、HTML文書の要素を扱う単純なモデルとして、
HTMLElement
クラスを定義する例を用います。評価基準の変化がクロージャーでどのように起こるのかを見ていきます。また、HTMLについての興味深い話ですが、2年前にHTML5が開始されなかったという話題がありました。HTML4からの大きな変更点として、
<article>
タグや<section>
タグ、最初の宣言についての変更があり、多くの方が経験した通り、HTML5はかなり違っています。そのHTML5が開始されたとはどういうことかと思いきや、実際には2つの仕様団体の分離した状況が統一されたということでした。新しいタグが廃止されたというわけではなかったので、ニュースとして大げさに受け取る必要はなかったようです。次回は、さらに具体的な標準化の発生について学びたいと思います。それでは、お疲れ様でした。ありがとうございました。


