今回も引き続き クロージャー
まわりにおける 強参照循環
についてを見ていきますね。これまででそれが発生する可能性があることをおさらいして、今回は実際にそれを起こすサンプルコードを見ながらその中で具体的にどのようにして強参照循環が発生するのかを細かく眺めていきます。よろしくお願いしますね。
————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #310
00:00 Start 00:31 前回のおさらい 01:36 クロージャーで強参照循環が発生するとき 03:53 クロージャーで強参照循環を起こしてみる 08:21 サンプルコードの動きを見てみる 10:19 強参照循環を起こしている場面の解説 13:13 lazy var になっているところが気になる 14:35 過剰なカスタマイズ手段の提供は一貫性の維持を困難にする 16:30 lazy var を使う理由 19:39 遅延初期化はその実行タイミングを図るのが困難 —————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #310
今日は、「オートマチックリカレンスカウンティング」つまり自動参照カウントについて学んでいます。特に順番参照についてです。スライドでは「共参照順番」と記載されていますが、普通は「順番参照」という言葉が一般的かと思います。
今回は、クラスとクロージャーの共参照循環について詳しく見ていきます。以前のセッションで、こういった循環参照の状況が発生することを説明しましたが、今日は具体的にどのような場合に発生するのかを、クラスを実際に作りながら確認してみます。これは意図的に循環参照を引き起こすサンプルコードなので、普通に開発するときにはこのような構造は避けます。あくまでも学習目的のサンプルコードですので、その点はご了承ください。
まず、クロージャーにおける共参照循環の重要なポイントをおさらいします。クロージャーがクラスのインスタンスのプロパティに割り当てられると、循環参照が発生する可能性があります。これはよく見落とされがちな点です。ネットでの説明でも前提条件としてさらっと書かれていることが多いです。
具体的には、クロージャーをクラスインスタンスのプロパティに割り当てた際、クロージャーの中でそのクラスのインスタンスをキャプチャすると問題が起こります。この2つのポイントをしっかり押さえておきましょう。
一方、構造体の場合は、クロージャーをその構造体のインスタンスのプロパティに割り当てても、クロージャー自体が複製されるために循環参照は発生しません。ですから、構造体のインスタンスの複製が完了したら、そのすべてが解放されることになります。
今回は、クラス内でクロージャーを保持し、その保持しているクラスがクロージャー内で使用されるという状況を作ります。具体的なサンプルコードについては、HTMLエレメントクラスを作って、HTMLのタグ名やテキストを保持するごく単純なモデルを考えます。
クラスには、プロパティとして HTML の内容を生成するクロージャーを持ちます。このクロージャー内で self
をキャプチャすることに注意が必要です。クロージャーはクラスの一部として保持され、クロージャー内で self
を使用するため、循環参照が発生する可能性があります。このような状態を把握し、適切に管理しないとメモリリークを引き起こします。
このサンプルコードでは、意図的にセルフをキャプチャして、レジーバーで保持するという状況を作り出しています。しかし、これはサンプルコードのためであり、日常でこのようにエラーを避けるためにやるのは避けるべきです。今回は解説目的なので、このようになっていますが、実際の開発では注意が必要です。 現在のクラスでは、名前を示すネームプロパティが定義されています。HTMLについての知識がないと理解しづらいかもしれませんが、代表的なタグとしては、<h1>
がヘッダー要素に対応し、<p>
が段落要素に対応します。<br>
は改行要素ですね。これらはHTMLの仕様で決まっており、それぞれの要素が名前として保存されます。
また、オプショナルであるテキストプロパティも定義されており、こちらにはテキストを保存することができます。例えば、<h1>
タグの中にヘッダーのテキストを入れる必要があるため、テキストプロパティが使用されます。段落要素も同様で、<p>
タグ内に文章を入れます。なお、代表的な要素では特段何も入れないこともありますが、それについてはHTMLの仕様に従います。
これらの基本的なプロパティに加えて、azhtml
というチェーン評価のプロパティも定義されています。ここでは、ネームとテキストを加工し、HTMLソースコードとして適切にモジュレートするためのクロージャが使用されます。このような仕組みにより、適切なHTMLソースコードを生成することが可能となっています。
HTMLプロパティの解説としては、デフォルト値を入れている点もあります。以前に解説したクロージャは、与えられたタグにテキストが含まれていればそれも一緒に表示されますが、テキストがなければ含まれません。この部分では、Optional型を使用することで、テキストの有無に応じた動きを実現しています。このクロージャによるHTMLが生成できるようになっているわけです。
さらに、特定のHTML要素で描画を変更するためにクロージャの差し替えが可能です。例えば、二進数から八進数に変換して表示させることもできます。出力テキストをカスタマイズしたい場合、クロージャを差し替えることでその実現を可能としています。しかしながら、このような差し替えは影響範囲が大きくなることがあるため、慎重に行う必要があります。結果として、オブジェクト指向が適している場合もあるかと思います。
以上のように、HTMLエレメントに対してデフォルトテキストを設定し、指定がない場合にそのテキストを使用する動きがありますが、すべての<h1>
タグが同じ動きをするわけではないので、特定の要素のみを操作する必要がある場合は工夫が必要です。 プログラミングにおける初期化のプロセスについて考えてみましょう。特に、遅延初期化(レイジー)について触れましょう。 <h1>
タグの変更では期待どおり動作したけれども、動かなかったケースがあった、という問題があります。これは、設定が分散されてしまう傾向に関連しています。今どきのプログラミング、オブジェクト指向やプロトコル指向、あるいはオートプログラミングとも言えますが、そういった分散的な方法論はあまり望ましくないと思います。
HTMLプロパティが遅延(レイジー)になる理由についてですが、遅延初期化は必要な場合のみ使用することをお勧めします。この理由を理解するためには、その使用がなぜ必要なのかを考えることが重要です。HTML出力で文字列を呼び起こすときだけ必要なので、通常は使用しないことが推奨されます。しかし、それも理由の一部に過ぎません。
遅延初期化の背後にある理由として、クロージャ内でself
を参照する必要があります。普通の初期化のフェーズでは、self
がまだ初期化されていないためクロージャ内で使用することができません。そのため、遅延初期化を用いて、インスタンスが完全に初期化されるまで初期化を遅らせることが必要になります。これにより、使用する時点でself
が初期化されていることが保証されるわけです。
遅延初期化を使うと、初期化のタイミングが実行時に決定されるため、コンパイルエラーの段階で問題を捉えることが難しくなります。したがって、コンパイルエラーの段階で品質を高めるためには、レイジーを避け、通常の変数定義を使うことが推奨されます。それにより、ソフトウェアの品質が向上します。
最後に、HTMLエレメントクラスのイニシャライザーについてですが、単一イニシャライザーを持つということは、初期化のエントリーポイントを限定することに寄与します。この制約は初期化の観点から非常に重要です。インスタンスが解放されたときにメッセージを表示するイニシャライザーも提供します。このように、コードの初期化や解放のプロセスを視覚化することができます。
次のステップとして、レイジーバーを使う場合には更なる改善を考慮し、コードを実際に作成してテストすることをお勧めしますが、ここでは省略させていただきます。お疲れさまでした。ありがとうございました。