前回は 循環参照
の概要とそれを実際に作ってみたりしましたけれど、今回はそれを解消していく方法や考え方についてを眺めていきます。お馴染みな機能で何気なく使えるものですけれど、それだけに改めて見直してみると良いことありそうな気がします。どうぞよろしくお願いしますね。
——————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #215
00:00 開始 00:18 今回の展望 02:37 強参照循環が起こる理由を理解は大切 03:14 強参照循環が偶然に作られる例 05:13 ところで、型に属するプロパティーの記載のしかたは? 08:35 定数プロパティーも気になるところ 10:26 オプショナル型の既定値 12:18 フィールドの初期値とイニシャライザー 14:55 初期化のカスタマイズ 17:13 オプショナル型の初期化タイミングは探れなさそう 21:21 オプショナル型で参照していない可能性を表現 23:07 解放されたタイミングを把握するための仕掛け 23:28 デイニシャライザーの日本語訳は? 24:46 国外での部屋番号の表し方 25:05 強参照循環を検証するための準備 26:27 John Appleseed の表し方 29:20 クロージングと次回の展望 ———————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #215
はい、それでは始めていきますね。今日は循環参照について話していきます。具体的な循環参照の解決方法については、前回実際にコードを書きながら試しましたので、今回はその内容を踏まえながら、循環参照をどうやって解消するのかを見ていきたいと思います。
画面に表示されているのは、前回最後に見たスライドです。注目しやすいところとしては、循環参照がゼロにならないコードを書くことが可能かどうかという点です。この、一般に「循環参照」と呼ばれる問題について対処するための方法を見ていくのが今回の目的です。
循環参照は、インスタンス間で両者が強い参照を持っていると起こりがちです。これを回避するためには、どちらかのインスタンスで強い参照を「弱参照(weak
)」や「無所有参照(unowned
)」に変える必要があります。この方法を学ぶことで、循環参照を解消することができるわけです。まずは循環参照がどうして起こるのかを理解し、その解決方法としての「弱参照」と「無所有参照」について学んでいきます。
循環参照の具体例として、アパートと住人をモデル化したコードを見てみましょう。ここでは、「Person」と「Apartment」というクラスを定義します。Person
クラスは名前(name
)をプロパティに持ち、Apartment
はオプショナル型のプロパティとして管理されます。これにより、住人がどのアパートに住んでいるかを記録できます。
このコードでは、循環参照を調べるためにイニシャライザーでプリント文を使って、オブジェクトが解放されたかどうかを確認できます。具体的には、Apartment
クラスではユニット番号(unit
)をプロパティに持ち、そのアパートにどの住人(tenant
)がいるかを保存します。
ところで、このコードについて少し話が逸れますが、init
の後にvar
があり、let
がその前にあるのが気になります。これらがプロパティであることは間違いないのですが、順番について何か意図があるのでしょうか。let
とvar
の間にinit
が入っているのは、何か特別な理由があるのでしょうか。サンプルコードだからかもしれませんが、この書き方には違和感がありますね。普通はそれぞれのプロパティをきちんと揃えたいものですが。
また、名前(name
)がlet
になっているのも気になります。名前が変更できないようにしたいという意図は分かりますが、普段はstruct
で作ることが多いので、このようなクラスの使い方は少し特別な状況なのでしょうか。今回は循環参照の例として無理やりクラスを使っているのかもしれませんが、それでも違和感を感じますね。
この辺りの細かい点については、次のセクションで説明していきます。何か気になる点があれば、いつでも質問やコメントを入れてください。次に進みましょう。 こんなふうにして、パーソンとパーソンクラスの解説をします。全てのパーソンインスタンスは名前のプロパティと、nil
で初期化されたアパートメントプロパティを持っています。
オプショナル型の初期値は何も指定しないとnil
になってくれます。これが大事な特徴で、このおかげでイニシャライザーの中でnil
を代入しなくても初期化が完了します。
もう一回プレイグラウンドでちょっとおさらいしてみましょう。要するに、通常の型の場合、デフォルトの値が設定されないという特徴がありますが、オプショナル型の場合は初期値が何も指定しないとnil
になるという特徴があります。これにより、イニシャライザーの中でnil
を代入しなくても初期化が完了するという状況になります。
また、イニシャライザーについてもう一度考えると、保存型プロパティとイニシャライザーを持つクラスのインスタンスは初期化が完了しています。これらは一体化されるのが自然な感じがします。特にJavaでは、イニシャライザーとコントラクタの2つのフェーズがあり、フィールドを初期化するフェーズと、イニシャライザーが担当するフェーズとが言語仕様的に明確に分かれています。
このように、Swiftではオプショナル型の初期化やイニシャライザーの使い方について理解しておくことが大切です。コピーライト: 株式会社ゆめみ そのときに、第一段階がこちらなんですよ。第二段階が「そういう人」というイニシャライザーという流れになっています。それを踏まえると、ブロック間隔がなんとなく反目的にある気がします。やはり、let
だろうとvar
だろうと、まずお隣同士に書きたいなという感じがしますね。
また、そういう人も同じです。その段階的にどうエラーが出ているのか見てみると、まず最初に宣言の所の初期値が優先されて対応されています。その後、イニシャライザーに入ってくるので、ここで再度初期化しようとするとエラーという状況になります。このように、意味的にさっきのアプローチは微妙だと感じるのも無理はないですね。
今のスライドの初期化の話ですが、オプションがある方、つまりイニシャライザーで特にnil
を入れなくても初期値としてnil
が入っているというのがあります。下にapartment = nil
という例を挙げましたが、ブロック的な意味合いでは、初期値としてまずnil
が入ってイニシャライザーに入ってくると考えると良いでしょう。
ただ、表向きには分かりづらい点があり、間違えるかもしれません。例えば、イニシャライザーの中で初期化した場合、この初期化が宣言時にされ、それからイニシャライザーの中で再度初期化されるという動きになります。これをカスタマイズするという方法もあります。オブジェクト指向でオーバーライドする際に、初期化済みの値を再度書き換えるという状況が出てきますね。このように段階的な初期化フェーズの中でも、元の初期値をカスタマイズすることになります。
ただし、この2つのフェーズを合わせて初期化フェーズとし、その中で何度カスタマイズしても最終的には1回の初期化で済むという流れになります。イニシャライザーの細かい動きは主にリセットのところで現れてくるのですが、これが2段階のイニシャライザーのフェーズが構成されているために起こります。
具体例として、オプショナル変数の場合、何も設定していなければnil
が入ります。ただし、IUO(Implicitly Unwrapped Optional)を使うと、これは自動的にnil
が入るわけではありません。var
の場合はnil
が自動的に入りますが、let
の場合は自動的にnil
が入ることはないので、エラーになります。
初期化が終わったかどうかを確認する方法として、自分自身のメソッドを呼べるかどうかを見ることができます。例えば、初期化後のイニシャライザー内でself
のメソッドを呼んだときにコンパイラが問題なく通過するかどうかで、初期化が正しく終了しているかを確認できます。最後まで実行できた場合、それは大丈夫だということになりますね。
以上が、初期化フェーズについての説明となります。 これでイコール nil
を消してしまうと、ここで「まだ実装できてないよ」ということでエラーになりますが、全然最初の問題を試せていないですね。ここでブレークアウトさせたら当然初期化が終わるわけで、実行できるでしょう。当たり前ですよね。
実はこれは var
を使う場合を試したかったのですが、nil
が自動的に入ってしまうので実行できてしまいます。これを2回やろうとして、1回目がちゃんと nil
が実行されなかったかどうかを中間言語でも確認しないとちょっとわからないですね。この確認方法は何かあるのでしょうか。
現在のプレイグラウンドで nil
が2回入っていないことをチェックする方法はあるのか考えてみましたが、リセットでは駄目でしょう。リセットは初期化フェーズでは発行されない特徴があるので、この初期化フェーズの中でいくら試しても1回も実行されません。オブジェクト指向でオーバーライドした場合はちょっと違いますが、基本的には試せません。
プロパティラッパーは余計な機能が挟まってくるので、純粋にオプショナルだけを検証することができないのです。なんとかできないかなと思っていますが、とりあえず初期化が必要であることを確認しただけにしておきましょう。
次に進むときにアパートメントプロパティを持っていて、最初は nil
が入っています。つまり、全ての人がアパートに入っているとは限らないので、入っていないことを表現するために nil
が入るということですね。アパートメントはユニットという形で部屋のブロックを記録することになっているらしいです。外国のアパートの一般的なシステムではユニットという表現を使いますが、日本では1DKなどのように表記します。
例えば、これを忘れた場合、思い出したらまたお話しします。プロパティに関してですが、契約者のことをテナントと言います。英語の意味として、日本では商業目的で利用することもありますが、一般的にはテナントという言葉を使います。
同じように nil
で初期化されています。住人が必ずしもいるとは限らないので、オプショナルパターンとして nil
が入るようにしています。これを踏まえて、本当に解放されるかどうかをチェックしていくのがポイントです。
リニットが入っているかどうか、これが問題ですね。 確認用ですね。ところで、いくつかの言葉が出てきたときに日本語訳を試してみたいと思っています。「イニシャライズ」を日本語で訳す人っていますか?どうなんでしょう。「イニシャライズ」をそのまま日本語で訳すと「非初期化」っていう言葉が見つかったり、「初期化の取り下げ」、「脱初期化」といくつか日本語訳をGoogleに聞いてみると、そんな言葉が出てきます。でも、本当にそんな言葉を使うのかなと疑問に思います。「非初期化」や「初期化の取り下げ」、「脱初期化」という言葉にはすごい違和感があります。
個人的には、「修了書類」とか「年末」とかのほうが似合うかなと思いながら、「イニシャライザー」の訳を考えていました。何かいいアイデアがあれば教えてください。
とりあえず、「イニシャライザー」、これはこんなふうに定義していますよ、ということで次に行ってみます。
さて、ここで出てきた「ユニット」、ユニットは部屋番号のようです。4Aとかそういったのがあるらしいですね。なるほど、とりあえず見ていきましょう。
次のコードでは「ジョン」と「ユニット4A」というオプショナル型の二つの変数を定義しています。これがオプショナル型であるのは単純に循環参照のテストのためにオプショナルにしているだけで、日本ではあんまりオプショナル型にはしない気がしますね。例だということで、こんな感じ。
例だとはいえ非常に気になりますが、それは後で理解しましょうか。二つの変数があり、これらはアパートメントとパーソンのインスタンスをそれぞれセットするのに使います。両方ともオプショナル型だから初期値がnil
になっているよ、といったことが書いてあります。やけに初期値nil
を強調するスライドですが、そこまで重要な気もしないです。でも参照のお話には重要なのかもしれません。
さて、とりあえず特定のパーソンとアパートメントのインスタンスを作成して、このインスタンスをジョンとユニット4Aの変数に割り当てるという感じの例になっています。しかし、この「ジョン」という変数名はやりすぎですね。でも、let
だから良いのかな。ネームがlet
で変化していないから良いのかな。
プロパティがその人の名前を持っているわけですから、ここはジョンって名誉ってもし中身が違ったら誤解を招くコードになります。変数名で「ジョン」というなら、他にももっと良い表現があるでしょう。たとえば、static let john = Person(name: "John Appleseed")
というふうにして、変数として名前空間に入れることで、コード全体が統一されます。
とにかく、変数名としてジョンはちょっと良く表現しすぎていて気になるので、もう少し工夫が必要です。たとえば、アパートメントの変数名も同じで、let
を使ったほうが整合性が保てますね。
ユニット型を作成したので、これで準備ができました。こうしたときの参照の要素については、時間の都合で次回またゆっくり見ていきましょう。これからどういう参照の状況になって、これを解消するにはどうしたら良いかについて引き続き次回でお話ししようと思います。
それでは、時間になったので今日はこれで終わりにします。お疲れさまでした。ありがとうございました。