本日も引き続き
強参照循環
を解消する手段のひとつの無所有参照
を オプショナル
で扱うことについてを眺めていきます。前回に具体的な様子を眺めましたので、本日はそれを踏まえて整理していく感じの回になりそうです。よろしくお願いしますね。————————————————————————————————
熊谷さんのやさしい Swift 勉強会 #242
00:00 開始
00:41 前回に見た相互参照の様子
02:32 無所有なオプショナル参照の特徴
03:20 無所有参照とオプショナル型とは別々に考える
05:53 解放されていないことの保証はプログラマーが行う
07:13 GitHub Copilot を使い始めた
09:12 強参照を解消してみる
18:44 弱参照であればエラーにはならない(正しいかは別問題)
20:10 チェーンを繋ぎ換える処理が必要
26:02 クロージングと次回の展望
————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #242
今日は循環参照についてのお話をしていますね。無所有のオプショナル参照についても見ていきましょう。前回は
unowned
を使うことで循環参照を回避する方法を確認しましたね。デパートメント(学科)とそのコースのインスタンス間の参照関係について見ていきました。デパートメントがコースを持ち、そのコースも次のコース(ビギナー、インターミディエイト、アドバンス)を持っているというモデルです。このモデルでは、デパートメントからの参照だけを共参照にし、それ以外の推奨コースは
unowned
で持たせることで共参照の循環を避けています。これによって、コースがどの学科に所属しているかという情報を持たせつつ、循環参照を防ぐことができる構造になっています。無所有のオプショナル参照についても、クラスの設計コードを見ておくと良いでしょう。
class Department {
var course: Course?
}
class Course {
weak var nextCourse: Course?
unowned var department: Department
}
このように、通常のオプショナルではなく無所有参照を
unowned
で持たせることによって、ARC(自動メモリー管理)によるスムーズなインスタンス管理が可能です。ただ、無所有オプショナル参照にすることでnil
が代入できるようになりますが、それ以外の動きは通常の無所有参照と同じです。ここで大事なのは、オプショナルと無所有の性質を混同しないことです。オプショナル参照は
nil
にできる点以外は無所有参照と同じ動きをします。無所有のオプショナル参照はnil
にできるという特性を持っていますが、それ以外は無所有参照と同じだと理解してください。次に、解放されていない参照は常にエクスプレスで参照することを確実にする責任があるという話です。オプショナルではない無所有参照と同様に、参照が確実に存在することをプログラマーが保証する必要があります。これは
unowned
を使用する際に非常に重要なポイントです。たとえば、デパートメント構成からあるコースを削除する場合、他のコースがその削除したコースへの参照を保持している可能性があるため、すべての参照を取り除く必要があります。これはプログラマーがしっかりと管理しなければならない部分です。
最後に、デモ環境について少しお話ししますね。ディスプレイの設定やデバッグについても話がありましたが、たまに表示がうまく行かないこともあります。こんな時は、設定を再確認することで解決できることが多いです。
それでは次に進みましょう。 さて、それでは具体的な内容に移ります。ここでは、特定のクラスのインスタンスが消えるときの挙動について説明します。インスタンスが消えるとネクストコース(次のコース)に影響を与える可能性があります。これがとても重要なポイントです。
まず、邪魔になりそうなアクセスコントロールを一時的に消しましょう。この状態で、具体的にどういう話になるのかを見ていきます。以下のようにコードを書いてみます。
class Course {
var name: String
var next: Course?
init(name: String, next: Course? = nil) {
self.name = name
self.next = next
}
}
let advanced = Course(name: "Advanced")
let intermediate = Course(name: "Intermediate", next: advanced)
let intro = Course(name: "Intro", next: intermediate)
// プリント処理
print(intro.next?.name) // Intermediate
print(intermediate.next?.name) // Advanced
print(advanced.next?.name) // nil
ここで、各コースの次のコースを確認します。イントロダクトリーの次はインターメディエイト、インターメディエイトの次はアドバンス、アドバンスの次は何もない(
nil
)状態です。このように、コースを削除するとどうなるかを見てみます。インターメディエイトコースを削除すると、その次のコースに影響があるかどうかを確認します。
var courses: [Course] = [intro, intermediate, advanced]
courses.removeAll { $0.name == "Intermediate" }
上記でインターメディエイトを削除しましたが、これにより次のコース(アドバンス)のリンクはどうなるでしょうか?削除後の確認を行います:
print(intro.next?.name) // nil になるかもしれませんが、依存関係が切れることに注意が必要です。
この結果、イントロダクトリーの次が
nil
になるかもしれません。ここで確認するために、もう一度ビルドしてみて、削除が正しく影響を与えるかどうかをテストします。print(courses.count) // 2
コースの数は2つに減っていますが、正しくリンクが切れているか、ネクストコースがどうなっているかを確認する必要があります。インターメディエイトを削除した後でも、イントロダクトリーの次に何か不整合があれば修正が必要です。
これは、プレイグラウンドなどでテストすると、残った状態がわかりにくくなることもあります。したがって、実際のプロジェクトでも同様の確認を行い、万全を期する必要があります。 実行をかけてみましょう。実行、実行。こちらでですね。これでオッケーです。
最初のコースの要は「イントロ」のネクストコースが「インターメディエイト」のままなので、それが解放されてしまうことによってエラーが発生してしまいます。だから、こういうときにはちゃんとつなぎ替える必要があるのです。プログラマーがちゃんとやらないと、重大なエラーになってしまうことがあります。
この場合、
weak
だとエラーにならないことがあるのでお伝えしておきますが、weak
なら解放されたとたんにARC(Automatic Reference Counting)が自動で弱参照を設定してくれる仕組みになっています。これによって、実行してもちゃんと動作しますが、これはこれで問題です。イントロの次が空っぽで分かりにくくなります。だから、マッシングとかにするか悩みます。こうするとメニューで見えますが、イントロの次が分かりにくいですね。イントロの次が「マッシング」としても、インターメディエイトを消した後に次が「アドバンス」になります。この場合、イントロの次がアドバンスという意味合いになります。本来はインターメディエイトをなくそうという感じでしたが、
weak
にしてしまうと問題が生じています。ですから、これをちゃんと対応するためには、例えば
if let
で固定のリテラルを使用するのは変ですが、どうなったのか見落としてしまいました。まあいいです。見逃さないように注意するのが難しいですね。全然ここに目が行かないです。こうしたときにはタラタラのネクストコースが時間2を入れたいわけではないのです。タラタラのネクストコースのネクストコースを入れたいのです。
return true
。そうじゃなければreturn true
です。アバウトのほうがいいでしょう。guard else return true
ですね。こっちのほうが良いとしましたが、必ずしもそうではありません。まあいいか。こうしましたが、ちゃんとチャットダウンになりました。これはアンオール、違いますね。違う話題にする場所が間違っています。そういうやつだとしょうがないので、1つ前に入れないといけません。これじゃだめですね。全然だめです。この1つ前に取れないんですね。さすがにネクストコースだと次を。
そうですね。ちょっとだめでした。もう少し予約をしておかないといけませんでした。リムボールじゃだめでしたね。だから、ゆっくり考えれば分かるのでしょうが、リムボールではだめですから、ゆっくりフォールアップで回さないとだめです。面倒ですが、こういったところをちゃんと管理しないといけないのです。
reset
とかでいけないのか。reset
で代入されたら前のデータを取って削除する。気持ち悪いことが出来上がりそうです。とりあえずここに戻して、これでオッケー。そうか、
reset
何だっけ。previousCourse
というのがやりたいのですが、previousCourse
これで何だっけ。リファレンスが取れるんですよね。今、オールドバリューのリファレンス。何だっけ。マイネットを取るのはオプショナルですね。ネクストコース。ここじゃないです。
reset
を登録したほうがこっちです。こっち、こっち。reset
でもあまりいい感じがしません。オールドバリューのリファレンスフロム、ニューバリュー。だから自分自身のコースでリファレンスフロムニューバリュー。オールドバリュー。バリューはいらないかな。これでいいのかな。こうすると、パラメータはequatable
ですね。いろいろとやっていかないといけません。この
reset
を置いて、前のデータを取ってリムーブして、残ったデータとでいろいろとやる方法もありますが、どうでしょうね。こういった方法の一つとして、addCourseといった感じで、removeCourseというアクションを用意してその中でうまくやるのも良いでしょう。細かい話は今回は省略しますが、とにかくこうやってちゃんと自分で取り除く、取り除いたときにネクストコースからも取り除き、不整合がないようにする。それをプログラマーが確実に行う責任が出てくるというのが注意すべきポイントです。今回はそんなところですかね。とりあえず、このサイドの方だと読めていないですが、読む必要があるかどうか。既に話しているかもしれませんし、既に話してある内容です。次回またお話しします。今日はちょっと時間がなくなってしまいましたので、今日はこれで終わりにしますね。
お疲れ様でした。ありがとうございました。


