本日は 強参照循環
を打開するための個々の手段についての続き、 無所有参照
についてを見ていきます。こちらについても 弱参照
と同様、クロージャー
で使う機会はときおりありますけれど、型に所属する プロパティー
で使う機会は自分はしてこなかった気がするので、そんなあたりも意識しながら眺めてみようと思います。よろしくお願いしますね。
————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #231
00:00 開始 00:09 今回の展望 00:42 プロパティーで無所有参照、使ってる? 02:22 参照型を内包するオプショナル型は、無所有が可能 05:28 通常の列挙型なら強参照 07:34 生存期間を意識した参照 11:41 クロージャー側で参照方法を調整する 15:15 無所有参照とは 16:17 無所有参照は値が常にあることを期待する 16:43 値があることを確実に保証しないといけない 18:35 クロージングと次回の展望 —————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #231
では、始めていきましょう。今日は「無所有産証」、いわゆるunowned
というキーワードについて勉強します。もともとこのセクションは、循環参照を解消するための方法を調べている中で出てきたものです。前回までにweak
参照を見たので、今日はunowned
参照を見ていくという流れになります。
このunowned
参照についてですが、以前の勉強会でも話しましたが、私はクロージャーのキャプチャリストで使ったことはありますが、オブジェクトのプロパティとして持たせることは、うっかりするとあまり経験がないんですよね。実際にコードを書いてみますが、この価値がどれくらい重要なのか理解するのが難しい部分もあります。
weak var object: Object
このように書くことはあります。しかし、
unowned var object: Object
これはあまり書いたことがないですね。使いたいと思えるところがあれば良いのですが、その発想もあまり湧いてこないです。これは一つの原因として、オプショナルにすることがあまりないというのもあるかもしれません。
unowned
参照はオプショナルではなく、暗黙的にアンラップされたオプショナル扱いになるため、使用するには少し注意が必要です。具体的には、このunowned
参照を使う場面としては、メモリの管理を明確にしたい場合や、循環参照を避けたい場合に有効です。
ここで少し脱線しますが、このunowned
参照がどのように問題なく使えるかについて考えると、非オプショナル型で定義されるので、循環参照の問題が発生しにくいというのがポイントです。しかし、それが正しいかどうかを実際に試して確かめる必要があります。
class MyClass {
unowned var myObject: MyObject
init(myObject: MyObject) {
self.myObject = myObject
}
}
このように定義した時に、ランタイムエラーが起こらないかを確認する必要があります。特に、オプショナル型と比べてunowned
参照がどのようにメモリ管理されるかについても気になります。
さらに、以下のようなコードを書いてみて試します。
class AnotherClass {
weak var object: AnyObject?
init(object: AnyObject) {
self.object = object
}
}
このようにweak
参照と比較してみると、 unowned
参照を使用することで、メモリ管理とパフォーマンスの違いが見えてきます。特にunowned
参照は、所有する必要がないオブジェクトには適しており、メモリリークを防ぐ手段の一つとして非常に有用です。
結論として、unowned
参照をどのように使うか、そしてweak
参照との違いを理解することが、Swiftのメモリ管理の基礎を押さえる上で重要になります。今後のプロジェクトでどの参照方法を使うかを適切に判断するために、これらの知識を活用していただければと思います。 オプショナルの特例で、クラス型、つまり参照型をオプショナルで扱うと、ヌルポインタも扱えるアドレスを保持する型になります。この特性があるため、全体を参照型として見たときに生存期間が重要になってきます。この動きが面白いですね。オプショナルは思ったよりもいろんなことができます。
例えば、ジェネリクスを使っても同じです。ここは型パラメータを入れることもできますね。型パラメータを入れることで、クラスにバウンドされたジェネリクスとしても使えますが、少し混乱するかもしれません。
一方で、生存期間の問題があります。例えば、オブジェクトのほうがバリューのストラクトよりも長く生きている必要があります。これは、NSアプリケーションやNotificationCenterなどを使う場合には特に注意が必要です。NotificationCenterのデフォルトであれば、アプリケーションが動いている間はずっと生きているので、そのまま使うことができますが、開放されたときには落ちる可能性があります。したがって、特にテストコードを書く際には注意が必要です。
通常、Strong Reference(強参照)とUnowned Reference(無所有参照)については、後者のほうがオーバーヘッドが少ないため、軽量な実装が可能かもしれませんが、NotificationCenterやNSアプリケーションの場合、アプリケーションが動いている間はデフォルトでずっと生きています。これを考慮に入れてコーディングする必要があります。
テストコードを書く際には、NotificationCenterを独自にインスタンス化した場合、それがバリューよりも長く生きていることを保証しないといけません。これが疎かになるとバグが生じる可能性があります。
項目としては、バリュー型が存続している間、NotificationCenterも存続させておかないとテストが正しく動かないので、それを通常の変数(var
)にしておけば、バリューが生きている間は必ず生きるため、安全なコードになります。ただし、テーマの一つである循環参照の問題が出てくる可能性もあるため、扱いが難しいところです。 循環参照が起こってプロダクト全体で問題が発生する場合、「アンオウンド」を使って回避することができます。ただし、具体例であるノーティフィケーションセンターのケースでは、問題が発生しないことが多いでしょう。通常、AddObserver
のメソッドを使用して、ノーティフィケーションセンターに登録するときには回避策が取られているからです。
一般的に、AddObserver
メソッドでは、name
パラメータに例えば NSWorkspace
のノーティフィケーションを渡し、object
パラメータも指定します。これにより、循環参照が発生する可能性がある場所で対策を講じます。ここで、キャプチャリストを使用して、例えば unowned
キーワードを使うと、強参照を回避し、値が存在する場合に限ってアクセスすることができます。
通常の参照とは対照的に、unowned
キーワードを使うとインスタンスを強く保持しません。これは他のインスタンスのライフサイクルが同じかそれ以上の場合に使用するのが良いです。例えば、同時に解放されることが確実である場合などです。そうしないと、予期せぬバグやエラーが発生する可能性があります。
次に、unowned
キーワードの機能について説明します。これは weak
参照と異なり、値が常に存在することを期待します。値がなくなると、ランタイムエラーになります。この点では、オプショナルとは異なり、プログラマが値が存在することを保証する必要があります。これは、厳密に管理しないと危険です。
また、unowned
でキャプチャするクロージャーの場合、値が開放されても nil
になりません。そのため、バッドアクセスを引き起こす可能性があります。これが非常に危険な部分で、インスタンスが解放されていないことが確実なときにのみ使用するべきです。また、解放済みのインスタンスにアクセスするとエラーになるため、この点にも注意が必要です。
今回は unowned
の基本的な特徴について説明しました。次回は具体的な unowned
の使用例について詳しく見ていく予定です。それでは、今回の勉強会はこれで終了します。お疲れさまでした。ありがとうございました。