今回からは本流に戻って、これまでに見てきた The Swift Programming Language の続きのところ、循環参照
の解消方法についての理解を深めていきます。多くの人が慣れ親しんでいる基本的なところですけれど、日常で多く登場するところでもあるので、この機に整理し直してみると良いことあるかもしれないです。よろしくお願いしますね。
———————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #225
00:00 開始 00:10 今回の展望 01:00 データ競合を防ぐためのよく見る定石 04:35 強参照循環を解決するための手段 05:20 Actor でも強参照循環は起こり得るはず 06:21 Actor も参照管理がされている 08:21 弱参照と無所有参照の使い分け 12:06 インスタンスの生存期間を意識して使う 14:21 変数に unowned を添える機会はどれくらいある? 14:56 @IBOutlet と unowned は併用しない 15:52 weak なら nil になってくれる 18:05 メモリー管理のオーバーヘッド 20:36 生存期間の制御は難しい 22:09 weak の処理コストがネックになる可能性は低い 23:01 オプショナル型にしたくないときにも unowned が使える 24:07 ギリギリまで unowned で攻めてみるのも練習として大事 28:55 クロージング ————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #225
では、始めていきます。今日は非常に久しぶりな話となりますが、順番参照についてお話しします。テーマとしては、自動リファレンスカウンティング(ARC)です。これまでにどのように順番参照が発生するのかについて説明してきましたが、今日はその解決策について見ていきます。
前回の勉強会で何か重要な話題がありましたかね?ああ、そうだ。レースコンディションの話ですね。データベースとレースコンディションについて話しました。ただ、その時はあまり気にせずに書いていましたが、改めて考えてみると、いろいろな技法が既に浸透しているので、レースコンディションを防ぐための手法も一般常識のようになっています。
具体的には、Swiftにおける値型と、その値が代入時にコピーされる仕組みによって、同時アクセスやレースコンディションといった問題が大幅に軽減されています。また、より一般的なオブジェクト指向プログラミングでは、EMUモデルを利用することで書き込みを防ぎ、レースコンディションを防止することができます。
さらに「右辺と左辺」という代入の仕組みによって、初期化を右辺に閉じ込めることで、データが不完全な状態で見えることを防いでいます。こうした手法により、レースコンディションが起こりにくくなっているのです。改めて考えてみると、プログラミング言語の設計や一般的な技法によって、安全性が高まっていることに気づきました。
ここまでの話について、何か詳しく聞いてみたいことがありますか?もし疑問があれば、遠慮なく質問してください。値型とコピーの話、EMUモデルの話、イニシャライザーなどについては、大体のイメージがつかめていればよいと思います。そういった技法によって、データ保護が実現されていることがお分かりいただければ大丈夫です。
では、ここまでの脱線は終わりにして、本題に入っていきましょう。今回のテーマは、参照の循環を解消する方法です。具体的には、「弱参照」と「無効化参照」について解説します。これらを利用することで、片方のオブジェクトを強く保持することなく参照できるようになります。これにより、参照の循環を防ぐことができます。
最近では値型がSwiftで増えており、アクターも参照型とみなされるので、アクターをプロパティとして持たせる場合にも、参照の循環に注意する必要があります。アクターについては私自身詳しいわけではありませんが、基本的にはクラスと似たような概念です。従って、強引用の循環を防ぐことが重要です。
次回はさらに詳しい技法について説明していこうと思います。今日はここまでにしますが、何か質問等がありましたら、お知らせください。 状態管理をして、それをコンカレンシーのレベルまで考慮して保護することが重要ですね。そうすれば、リソースが足りなくなることはないはずです。アクターも参照カウンターを持っていますよね。これについて調べてみましょう。
参照カウントがあるかを調べる方法ですが、アクターの場合、例えば myActor
というインスタンスがあるとします。そのとき、 let instance = myActor
のようにインスタンスを代入します。その後、 isUniquelyReferencedNonObjC
という関数を使ってチェックします。この関数を使うと、参照が共用されているか確認できます。例えば、以下のように書くことができます。
if isUniquelyReferencedNonObjC(&instance) {
print("参照は一意です")
} else {
print("参照は共有されています")
}
このようにして、アクターが参照を持っているかどうかをチェックできます。大した話ではありませんが、次に進みましょう。
次に、共参照の順番の解決手段について説明します。これは、他のインスタンスと参照の寿命の違いによって判断するものです。例えば、逆参照(weak reference)と無償参照(unowned reference)があります。逆参照は、他のインスタンスの寿命が自分より短い場合に使います。無償参照は、他のインスタンスの寿命が同じか長い場合に使います。
この判断基準を理解するのは重要です。逆参照と無償参照の違いを説明するための例として、アパートメントクラスがあります。アパートには入居者がいて、その生存期間によって参照方式が異なります。例えば、入居者インスタンスが解放される前にアパートメントが存在している場合、逆参照が適切です。
アパートメントクラスの例を確認しておきましょう。
class Apartment {
weak var tenant: Person?
}
class Person {
var apartment: Apartment?
}
このように、tenant
が逆参照として定義されています。入居者が出て行く場合に参照が解除されるためです。しかし、入居者の方が生存期間が短くなる場合が多いため、このような設定が適切です。
例えの難しさについても注意が必要です。要は、あるインスタンスが nil
になる可能性があることを意識しながら設計する必要があります。理解のために、適切なライフサイクル管理が必要です。 このスライドを見て思ったんですが、言われてみれば当然のことでも、生存期間について考えて weak
にするか unowned
にするかというのをあまり考えたことがなかったので少し怖いですね。特に無常予算書のときは、生存期間が長いかどうかについてあまり検討していなかったなと思います。
例えば、クラスの中にオブジェクトがあって、そこで新たに変数を作るとします。クラスのケースでは、構造体で定義したくなることが多いですが、ここはそのままにして、var item
とする場合、例えばそのアイテムがオーナーを持つとしましょう。話がややこしくなるので簡単に説明しますが、この場合、循環参照が起こることがあります。
例えば、自分でアイテムを持つ場合、循環参照を回避するために weak
にしてオプショナルにすることで対応することが多いですね。しかしクラスのプロパティに unowned
を付けるのはどれくらい実際に使われるでしょうか。昔、デザインを作っていたときは、unowned
を使ったことがありましたが、バグが引き起こされることもありました。
生存期間を意識しない場合、IBOutlet
はオプショナルを求めるため問題はないかもしれませんが、その他のシチュエーションでは unowned
を慎重に使う必要があります。例えば、Swiftのドキュメントには、銀行でユーザーと口座の関係についての例があり、ユーザーが口座を持っていて、その口座にユーザーがいなくなれば存在しない場合には unowned
を使うと示されています。
実際、unowned
を使うとクラッシュすることがありますが、アーキテクチャ次第で適切に使える場合もあります。制御できているなら unowned
も選択肢に入りますが、weak
だと参照がなくなった場合に nil
になる点があります。
また、クラスがサブクラスではなくて、オブジェクトがプライベートユニットのような設計になっていれば、生存範囲の管理がしやすくなります。これにより unowned
を使うかどうかが決まることがあります。さらに、コメントでも示されている通り、weak
テーブルの処理コストがネックになるケースもありますが、これが大きな問題でなければ weak
を使うのが一般的かもしれません。 感触やバウンドなど、短い時間的サイクルで呼び出されるオーディオユニットのコールバックバウンドといった場面では、あまりコストをかけたくないですよね。そういった場面では、確かに weak
のほうが安全だと感じます。
weak
を使うとどうしてもオプショナル型になりますが、それが気になる場合には unowned
を使うこともあります。しかし、weak
で自動アンラップされるオプショナルを使うと、その値が nil
になったときに落ちるので、あまり変わらないと感じますね。したがって、unowned
を使うことも確かにあります。
ただし、unowned
を使う際にはオブジェクトの生存期間をしっかりと考慮しないと怖い結果になってしまいます。生存期間を加味したうえで使うので、unowned
を付けるには度胸が必要ですね。しかし、安全性を何よりも重視すべきです。
普段、コードを書いているときには、オブジェクトの生存期間をあまり意識せずに unowned
を使ってこなかった気がします。今回の情報は個人的にはとても参考になりました。ぜひ、みなさんもギリギリまで unowned
で試してみると、コードの見通しが良くなるのでおすすめです。
もちろん、クラッシュすることが怖いのは理解できます。どこまで unowned
で攻められるかは慎重に判断しないといけませんが、その追求も大事なので、ぜひ試してみると良いと思います。unowned
が使えるところで正確に使うことは、確かにかっこいいですね。しかし、信用のレベル感を見極めるのは難しいです。ちょっとした変更で生存期間が変わってしまうと、その瞬間にクラッシュする可能性があります。
コメントでもあったように、weak
を使っているところで unowned
でも大丈夫な場合に、ワーニングとかで教えてくれるとありがたいですが、設計次第でクラッシュするリスクもあるので慎重であるべきです。
AI がもっと高度に発展すれば、プロジェクト全体を見渡して適切な提案をしてくれるかもしれませんね。しかし、現在のところは自身で生存期間をしっかり考慮しながら unowned
を使う必要があります。
こういった課題を意識してコードを書いていくと、研修や実践でも面白いかもしれません。もう一つのコメントでもありましたが、スコープが小さくないとリスクが高まるので、クラスプロパティとして unowned
を追求してみるのも良いと思います。
それでは、今日はこれで終わりにしましょう。お疲れ様でした。ありがとうございました。