はじめに
Swiftでは、オブジェクト同士が互いに参照し合うことでメモリリークが発生することがあります。この問題は「Retain Cycle(循環参照)」と呼ばれ、アプリのクラッシュやメモリ使用量の増加を引き起こす原因になります。
本記事では、Retain Cycleの仕組みとその対策として使われる weak
, unowned
について、コード例を交えながら解説します。
本記事に登場するARC(Automaric Reference Counting)についてはこちらの記事で解説しています。
強参照 (strong) とRetain Cycle
Swiftではメモリを参照する方法が2つあり、その1つが強参照 (strong)です。
強参照の特徴
- 他のオブジェクトが強参照している限り、メモリは解放されない
- Swiftのデフォルトの参照方法
この強参照とARCが組み合わさることで起こりうるのがRetain Cycle (循環参照) です。
Retain Cycleとは
- 2つのオブジェクトが お互いを強参照 していると、どちらの参照カウントも0にならず、メモリが解放されない=メモリリークになる現象
以下の例をみてみましょう。
class Person {
var name: String
var pet: Cat?
deinit {
print("\(name)は解放されました!")
}
}
class Cat {
var name: String
var owner: Person?
deinit {
print("猫の\(name)は解放されました!")
}
}
var jiro = Person(name: "じろう")
var meow = Cat(name: "ミャオ")
jiro.pet = mewo
meow.owner = jiro
jiro = nil // ⚠️ 何も出力されない
上はjiro: Person
がmeow: Cat
をpet: Cat?
として持っているといった例です。
deinit
はオブジェクトが参照されなくなったタイミングで呼び出されます。
最後の行でjiro = nil
としているのにdeinit
は呼ばれていません。これは、jiroがmeowから強参照されているためです。
weakとunowned
weak
とunowned
はオブジェクトの参照方法を明示的に指定することができるSwiftのキーワードです。主に以下の特徴があります。
キーワード | ARCカウント | いつ解放される? | Optional |
---|---|---|---|
weak |
されない | 参照先が解放されると解放 | Yes |
unowned |
されない | 参照先が解放されたらクラッシュする | No |
weak
を使って参照している場合、参照先のメモリが解放されると、自動的に弱参照が消えます。そのため、weak
を使うことでRetain Cycleを防ぐことができます。
weak
を用いて上記のコードを修正してみます。
class Person {
var name: String
var pet: Cat?
deinit {
print("\(name)は解放されました!")
}
}
class Cat {
var name: String
weak var owner: Person? // // 循環参照を防ぐためweakを追加
deinit {
print("猫の\(name)は解放されました!")
}
}
var jiro = Person(name: "じろう")
var meow = Cat(name: "ミャオ")
jiro.pet = meow
meow.owner = jiro
jiro = nil // 🔄 このとき Pet → Cat の strong参照がなくなり、Personも解放される
// ⭕️ "じろうは解放されました!"
meow = nil // 🔄 これで Cat も解放される
// ⭕️ "ミャオは解放されました!"
一方でunowned
を使って参照している場合、もし参照先のオブジェクトが先に解放されると、アプリがクラッシュしてしまいます。
unowned
を使うべきなのは、「参照先が自分よりも先に解放されない」ことが保証されているときです。
例えば、アプリ起動中ずっと存在し続けるような RootView
のようなオブジェクトに対しては、unowned
での参照が適しているケースがあります。
まとめ
-
Swiftではデフォルトで「強参照(strong)」が使われ、これがRetain Cycleの原因になることがあります
-
Retain Cycleとは、2つのオブジェクトが互いに強参照し合うことでメモリが解放されなくなる現象です
-
weak
は参照先が解放されると自動でnil
になり、循環参照を防げます(Optional型) -
unowned
は非Optionalで使える反面、参照先が先に解放されるとクラッシュするため、使い所に注意が必要です -
Retain Cycleのリスクがある場面では、
weak
もしくはunowned
を適切に使うことで安全なメモリ管理が可能になります
さいごに
お読みいただきありがとうございました!
この記事がSwiftでのメモリ管理やRetain Cycleの理解に少しでも役立てば嬉しいです。
私自身もまだ勉強中の身ですので、もし内容に誤りや不明点があれば、ぜひご指摘いただけると助かります。
一緒に成長していきましょう!💪
参照