循環参照の理解から対策までを記述しました。
ステップ1:メモリの仕組みを理解する
SwiftのメモリはARC(Automatic Reference Counting)と呼ばれる仕組みで管理されています。
ARCはオブジェクトを参照する数を数え、その数がゼロになると、そのオブジェクトは不要と見なされ、メモリから解放される。
これによって、プログラムが無駄なメモリを使わずに効率的に動作できます。
メモリの確保
ARCがメモリを確保するのはクラスのインスタンスを生成
したタイミングである。
そこから変数に格納されるとそのメモリ(インスタンス)の参照という形で参照数がカウントされる
。
以下、ゲームに例えてコードで実装してみる。
// キャラクタークラス
class Character {
var name: String
init(name: String) {
self.name = name
}
}
// メモリ上に勇者(オブジェクト)を生成。変数に格納されため、キャラクタークラスの参照数は1
var character1:Character? = Character(name: "hero")
// 参照数は1
再度、キャラクタークラスをインスタンス化して別の変数に格納する。参照カウントは2となります。
// メモリ上にスライム(オブジェクト)を生成。変数に格納されため、キャラクタークラスの参照数は2
var character2:Character? = Character(name: "slime")
// 参照数は2
Characterクラスは2つの変数から参照されている。
Swiftのクラスは参照型でデータを保持しているのでこの参照カウントはクラスに対してのみ行われます。
メモリの解放
インスタンスに対して1つでも参照しているものが存在する限りは、インスタンスを解放しない。インスタンスへお参照がゼロになったときに初めてメモリを解放します。
// character1にnilを代入したが、これではSwiftはメモリを開放しない
var character1:Character? = nil
// character2にnilを代入したことにより、参照がゼロになり初めてメモリを解放する
var character2:Character? = nil
上記の説明でswiftのメモリの仕組みが理解できたので、次は本題の「循環参照」について考えます。
ステップ2:循環参照とは
2つのインスタンス同士がお互いを参照しあっている関係を循環参照と呼ぶ。互いを参照しあっているので参照カウンタがゼロにならず、メモリを解放することができない。
本来もう使用することはないデータがメモリ内に永続的に残っていてしまい、不要なメモリが解放されない状態が続いてしまっている。
以下のコードで説明します。
Person
クラスとPet
クラスがあります。
Person
はペットを所有する(petプロパティ)、また、Pet
には飼い主がいる(ownerプロパティ)。
// 人間
class Person {
var name: String
var pet: Pet?
init(name: String) {
self.name = name
}
}
// ペット
class Pet {
var species: String
var owner: Person?
init(species: String) {
self.species = species
}
}
// メモリ上にjohn(オブジェクト)を生成。
var john: Person? = Person(name: "john")
// Personインスタンスの参照数:1
// メモリ上にdog(オブジェクト)を生成。
var dog: Pet? = Pet(species: "Dog")
// Petインスタンスの参照数:1
// johnのpetプロパティにdogを代入。
john?.pet = dog
// Personインスタンスの参照数:2
// dogのownerプロパティにjohnを代入。
dog?.owner = john
// Petインスタンスの参照数:2
上記により以下の関係性が設定されます。
プロパティにインスタンスを代入したことによって、下記の図のようにお互いを参照し合う関係になった。

変数john
にnilを代入して、Swiftのメモリを開放してみるが・・・
// nilを格納しても参照は0にならない
john = nil
この場合変数john
にnilを格納(Personインスタンスの参照を解放)しても、PersonインスタンスはPetインスタンスのプロパティに紐づいているため参照は0にならない。
Personインスタンスは、dog?.owner = john
により、犬の飼い主として設定されている状態。(参照されている)
さらにこのまま変数dog
にもnilを格納する。
それぞれのインスタンスを指す変数はもうnil
が代入されているので、これらのインスタンスにアクセスする手段は無く宙ぶらりんの状態で不要にメモリを占有しメモリリークしています。(循環参照)
// nilを格納しても参照は0にならない
dog = nil
上記により以下の関係性が設定されます。
インスタンスの循環参照が残り続けてしまいます。

ステップ3:循環参照の対策
循環参照の対策について説明します。
キーワードになるのが弱参照、強参照になります。
強参照:storong
ARCではインスタンスとデータの参照は基本的に「強参照」と呼ばれる強い参照で紐付けられ参照カウントが増やされます。そしてそのデータに対して強参照が残っている(カウントが0でない)限りメモリが解放されることはなく、また別インスタンスからの強参照により循環参照などが発生する原因になります。
弱参照:weak
強参照とは違い、弱参照は参照カウントを増やしません
。
参照しているオブジェクトが解放されると、弱参照は自動的にnil
になります。
このweakキーワードをプロパティ宣言の前に付与することで参照の強さ
を変更することができます。
以下、コードで検証してみましょう。
Pet
クラスのプロパティにweakキーワードを付与します。
また、値としてnilを許容する必要があるので型はオプショナル型である必要があります。
ついでにデイニシャライザが起動していることを確認するため、print文も追加しておきます。
※デイニシャライザは、インスタンスが解放される直前に呼び出されます。
// 人間
class Person {
var name: String
var pet: Pet?
init(name: String) {
self.name = name
}
// デイニシャライザを定義する
deinit {
print("Personクラスのインスタンスが解放されました")
}
}
// ペット
class Pet {
var species: String
/// 弱参照させる ///
weak var owner: Person?
init(species: String) {
self.species = species
}
// デイニシャライザを定義する
deinit {
print("Petクラスのインスタンスが解放されました")
}
}
weakを使うと、ARCの参照カウンタにカウントされず
に、参照をすることができます。
var john: Person? = Person(name: "john")
// Personインスタンスの参照数:1
var dog: Pet? = Pet(species: "Dog")
// Petインスタンスの参照数:1
john?.pet = dog
// Personインスタンスの参照数:1のまま
dog?.owner = john
// Petインスタンスの参照数:2
上記により以下の関係性が設定されます。
weak
使用前と比較すると、Personインスタンスの参照カウントが1減って1になりました。
ここで、変数john
にnilを代入すると、Personクラスのインスタンスが解放されます。
print("------------------------------")
john = nil
print("------------------------------")
// ------------------------------
// Personクラスのインスタンスが解放されました
// ------------------------------
上記により以下の関係性が設定されます。
Person
クラスのインスタンスは最初の状態で参照カウントが1だったため、変数john
にnilを代入した結果、Person
クラスのインスタンスの参照カウントが0になり、Person
クラスのインスタンスは破棄されます。
そして、Pet
クラスのインスタンスへの参照カウントが1減って、1になります。また、Pet
クラスのインスタンスが参照するPerson
クラスのインスタンスへのプロパティは弱参照となっているため、Person
クラスのインスタンスの破棄に伴いnilが設定されます。
この後は、変数dog
にnilを代入することで、Pet
クラスのインスタンスの参照カウントが0になり破棄されます。これでメモリリークすることはなくなりました。
print("------------------------------")
dog = nil
print("------------------------------")
// ------------------------------
// Petクラスのインスタンスが解放されました
// ------------------------------
参考