Page icon

第240回

2023/04/19
Empty
Automatic Reference Counting
Empty
Empty
Empty
Empty
Empty
12 more properties
本日はようやく本編に戻って、引き続き 
強参照循環
 を解消する手段のうちのもうひとつの
無所有参照
 を 
オプショナル
 として扱うことについてを眺めていきます。これまでにもときおり話題にしましたけれど、改めて思うと確かに微妙にアンバランスな感じがしたりするので、そんなあたりも気にしつつ見ていけたらいいなと思っています。どうぞよろしくお願いしますね。
——————————————————————————— 熊谷さんのやさしい Swift 勉強会 #240
00:00 開始 00:20 今回の展望とこれまでのおさらい 03:11 無所有参照をオプショナル型で使う 05:37 ARC における所有権の扱い 07:33 無所有参照なオプショナル型を扱う上での責任 08:48 nil とするかはプログラマーの責任 10:48 ギリギリまで突き詰めるのは良い勉強になる 12:42 無所有なオプショナル参照を使う例 13:41 イニシャライザーは値を確約するためのもの 16:17 このクラス間の関係性は妥当? 20:27 コード例の解説 23:32 コード例で用意したクラスを扱う例 25:13 より確実な初期化手続きにするには? 32:58 クロージングと次回の展望 ———————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #240
はい、では始めていきますね。今日は久しぶりに本編に戻っていきます。まあ、いい感じに進められたらいいなと思います。今回のテーマは「無所有参照」です。初めて耳にする方もいらっしゃるので、軽くおさらいをしますね。
もともとは「循環参照」について話していました。この勉強会では「共産性循環」とも呼んでいますが、二つのインスタンスが互いに参照し合うことで、例えばAからB、BからAといった形で、インスタンスが自動的に解放されなくなってしまう問題です。
これを解消するための方法として「弱参照」と「無所有参照」があります。これによって、共産性循環を断ち切り、インスタンスが使い終わったら自動的に解放されるようになります。そうすることで、プログラマーが強く気を使わなくても済む、という仕組みです。無所有参照の概要までは前回お話ししましたが、今日は無所有参照をオプショナルで使うという話をします。
振り返りすぎると話が進まなくなってしまいそうなので、このまま進めちゃいますね。ただ、前回話したからといって今回は説明しないというわけではありません。わからないことが出てきたら、気軽にマイクで質問していただいても、Zoomのコメントに投げていただいても大丈夫です。特にこういったオンライン勉強会では、質問するまでにタイムラグがあるかもしれませんが、10分後だろうと20分後だろうと気軽に質問してください。
では、無所有参照をオプショナルで使うという話を進めていきます。クラスへのオプショナルな参照は少々ややこしいですよね。オプショナルなクラス型でも
unowned
をつけられるという話です。
具体的には、値型のオプショナルにも
unowned
が付けられるということです。
weak
はオプショナル型にしか付けられませんが、
unowned
についてはオプショナルでもそうでなくても大丈夫です。基本的にはオプショナルでない方が一般的かもしれませんが、オプショナルも可能です。これについて話すのが今回のテーマですね。
このように明確に言葉として示されると、普段どう意識しているかに気がつくことがあります。スライドにしっかりと「オプショナルな無所有参照」と書かれているのは、良いアプローチだと感じます。無所有参照自体があまり馴染みのない方も多いかもしれないので、これから進める内容が楽しみですね。
ARC(Automatic Reference Counting)の所有権モデルについては、共産性、逆参照、無所有参照が存在しています。これが主要なポイントですね。 Swiftは参照カウントシステムでインスタンスの管理を行っており、無所有参照と弱参照は同じ文脈で使えることが多いです。共産性が問題になる場合でも、オプショナルなのでNilも入り、同じように扱えます。
それでは続けていきましょう。 無所有参照なオプショナルという言い方になったら良いですね。そのときにプログラマーが責任持たないといけないのは、次のことです。それが常に有効なオブジェクトを指しているか、もしくは
nil
がセットされているか。この2つを必ず守る必要があります。ただし、これが全てのように感じるかもしれませんが、以前の勉強会の内容を思い出してみてください。無効なオブジェクトを参照してしまわないよう注意が必要です。プログラマーがこの責任を負わなければなりません。これは結構重要です。アンオーンド参照の特徴ですが、オプショナルに限らず、必ず有効なオブジェクトを指しているか、オプショナル型では
nil
がセットされているか、これを確実にプログラマーが制御しないといけないのです。制御できないのであれば、使用すべきではありません。
弱参照との違いを思い出すと、弱参照の場合、無効なオブジェクトになったときに自動で
nil
がセットされます。この2つの条件をコンパイラが責任を持ってくれるのが弱参照です。オプショナルなアンオーンド参照はプログラマーがその責任を負わなければなりません。この2つの重さは大きいですね。コンパイラが保証する場合、現時点ではどの場面でも責任を果たせるように一般的な対応をしてくれます。もしその対応が論理上不必要であっても、コンパイラは無駄であってもチェックを行います。そのため、プログラマーに責任が委ねられる場面が重要です。
チェックが不要になった分、パフォーマンスなどが向上する場合もあります。無所有のオプショナル参照を使いこなすことが求められ、そのための練習は有益です。無所有参照を正しく使うには、プログラマーが広範な視野を持ち、コードの移動や処理についてしっかり監視する能力が必要です。もしそれができないと、アプリがクラッシュするなどの重大な問題が発生します。そのため、こういった技術の習得は大切です。
クロージャを使ったプログラミングにおいても、この視野が役に立ちます。例えば、ディスパッチキューにクロージャを投げたり、コールバックハンドラーを設定したりする際、それがどのように動くのかを理解することは重要です。このように、言語の仕様を学ぶと、より深い理解が得られます。
迷ったときには、一般的には弱参照を使うのが良いとされています。「迷ったら弱参照」というふうに覚えておくと安心です。これが一つのガイドラインとなります。
次に、大学の学科とコースを例にして説明します。ある学校の特定の学科に対してコースが設定される例を見てみましょう。例えば、教育学科、国文学コースなどがあります。イニシャライザで名前を設定し、コースを空にするような初期化を行います。個人的には、この例は少し微妙に感じますが、イニシャライズはそういったものです。後でコースを登録することもあるでしょう。
このような例で、学科に対してコースを設定する方法を学習しました。いろいろな視点から基本を押さえることが大切です。 とりあえず、学科とその教職課程の話についてですが、各学科が何に所属しているかを持たせるというのも一つの考え方です。そして次に、「次のコース」ということは、ビジネスコースやマスターコースといった感じですかね。とにかく、「次のコース」が提案され、初期化の際にはコースが必ず学科に所属しなければならないという流れでしょうか。このようにイニシャライザーも理解することができるでしょう。
話を戻しますが、学科は学科名だけ決めればイニシャライズが完了します。これで初期化は完璧だという主張です。一方、コース名とそれが所属する学科を指定して初めて初期化が完成するといった感じです。その後、通常の変数として書き換えることができます。ただ、オプショナルではないので学科が空になることは基本的にありえません。こうしたコードから設計意図を感じ取ることができるでしょう。設計が完璧でない場合は、設計を再考する必要があります。とりあえず、こんな感じです。
ここで話が少し脱線しますが、例えば学科からどのコースがどの学科に所属しているかを知る必要があるプログラムであれば、Department(学科)で持たせることが考えられます。この際に
unowned
を使います。これは学科の方が生存期間が長いためです。学科がなくなってコースだけが存在することは一般的にありえません。したがって、ここは
unowned
で問題ありません。
weak
を使う必要はありません。
次に、ネクストコースですが、これは便利上、おいているだけなのでしょうか。コースから学科を辿り、配列をインデックスアクセス可能にするためのデータ構造となっています。これで順番をハンドルしています。つまり、ビジネスコースが終わったら次のコースに進むという感じです。ここはセットにするかどうかは好みかもしれません。セットにすると順番が狂ったら問題が発生するため、セットにしないほうが良いかもしれません。
次に、なぜこの変数が
nil
になるかという点ですが、オプショナルになっているのは、次のコースがない場合(例:マスターコースで終了した際)に
nil
になるからです。ここで
unowned
とオプショナルを使うのは特段珍しいことではありません。
unowned
unowned
であり、オプショナルを使うかどうかは別の問題です。
では、次に進みましょう。今のコード解説に戻ります。Departmentは提供する各コースへの参照を維持します。この表現は少し変ですが、要するに、Departmentは各コースへの参照を保持するということです。ARCの所有権のマテリアルでは、学科が各コースを所有します。これで概ね理解が進むでしょう。
以上のように、Swiftの言語仕様と設計意図に基づくコードの解説を進めていきます。 Swift 言語の勉強会では、所有参照の概念について話しています。コースは二つの所有参照を持ちます。一つは部署(デパートメント)への所属、もう一つは学生が所属すべき次の課程です。例えば「マスターコース」や「ビギナーコース」といったものですね。大学における教育課程や博士課程のような仕組みを連想させます。
課程はこれらのオブジェクトのどちらにも所有されません。これは所有参照ではなく、弱参照を使っています。したがって、共参照として扱われないということです。すべての課程は部署に所属し、必ずどこかに所属するため、部署はオプショナルではありません。必要に応じてオプショナルを使用することは重要ですが、必ず所属するものを無闇にオプショナルにするのは誤りです。
一方、コースの場合は次の課程がないこともあるため、次のコース (Next Course) はオプショナルになります。これは次がない場合に
nil
を使って表現できるためです。
次の例題も見てみましょう。ここではガーデニング(園芸)、デパートメントのコースを定義します。具体的には、植物系の普及中級および上級といったコースですね。各コースには、イントロダクション (Introduction)、インターメディエイト (Intermediate)、アドバンス (Advanced) などがあります。これらはビギナーからマスターへとステップアップしていくイメージです。
まずインスタンスを作成します。イントロ (Introduction) コースには次のコースがインターメディエイト (Intermediate) で、インターメディエイトには次のコースがアドバンス (Advanced) と設定されます。アドバンスの次には何も設定されていないため、そこで
nil
が入ります。このように定義することで、「アドバンスの次はない」ということが示されます。
各コースはイントロ、インターメディエイト、アドバンスとなり、これをデパートメントに入れて設定完了です。とはいえ、もし登録し忘れるとバグの可能性があります。このセットアップをより安全にするために、何らかの確認メカニズムを追加するのが良いかもしれません。
例えば、コースを追加したときにデパートメントが管理するようにし、コース同士を関連付ける関数を使って管理することで、そのようなミスを防ぐことができます。
以下は、具体的なコード例です:
class Course { var name: String weak var nextCourse: Course? init(name: String, nextCourse: Course? = nil) { self.name = name self.nextCourse = nextCourse } } class Department { var courses: [Course] = [] func addCourse(_ course: Course) { if let lastCourse = courses.last { lastCourse.nextCourse = course } courses.append(course) } } let intro = Course(name: "Introduction") let intermediate = Course(name: "Intermediate") let advanced = Course(name: "Advanced") let department = Department() department.addCourse(intro) department.addCourse(intermediate) department.addCourse(advanced)
このコードでは、各コースが順次追加されていく際に、最後のコースの
nextCourse
プロパティが次のコースを指し示すようになっています。これにより、コースの連結を確実に行うことができます。 これでいい感じに追加されているでしょう。そうすると、勝手にここをいじってほしくない部分があるので、やっぱり
private(set)
かな。何も変えたくないですが、名前は変わることもあるかもしれません。変わることを考慮するならこれでいいですね。
コースだけは、インターフェイスがちゃんとオープンされているものだけを使うべきです。例えば、コースを受け取る可能性があるなら
removeCourse
とか、こういったメソッドを作って、ちゃんとこの
nextCourse
のチェーンもうまく管理し、間に中級コースが削除されたときには、初級から上級へリンクするようにとか、そのようなことを考慮する必要があります。
次に
nextCourse
ですが、これも勝手に触って欲しくないので
private(set)
ですね。デパートメント所属も勝手に触って欲しくないので同じように
private(set)
にします。これで大体いいかなと思います。
データを順番通りに伸ばしていいものとして、既に
nextCourse
が最初から設定されている場合もあるかもしれません。なるほど、他の場所から勝手に
nextCourse
を取ってきて入れるなど、色々と考えられますね。
さらに考えていくと、イニシャライザーまで
fileprivate
にして、
addCourse
でコース名だけを追加することにします。これだけで基本的な内容も保持できます。ここまでやると過保護すぎるかなという気がしてきます。しかしこの辺は全体を見たときのバランスだと思います。過保護すぎるかもしれないし、適度に保護することがあるかもしれません。
アクセスコントロールが複雑すぎるのも良くないので、どこまで保護すべきか考慮しつつ、適当な加減を取ることが最終的には実用的かもしれません。例えば、こんなふうにすると、インデックスオーダーでコースが並び、
nextCourse
の追加漏れもなくなり、データが安定した状態になります。そのような視点を持って、コードを書くと良い練習になると思います。
時間になったので今日はこれくらいにしましょう。次回は、引き続き具体的にインデックスの反省の具合がどうなっているのかを確認していきます。お疲れさまでした。ありがとうございました。