# Swiftのメモリ管理
# ARC | Automatic Reference Counting
Swiftは、クラスのメモリ管理に ARC (Automatic Reference Counting) という仕組みを利用しています。
ARCは、新しいインスタンスが作成されるたびに、値を保存するためのメモリを確保します。このメモリには、インスタンスの型に関する情報と、プロパティの値が保持されます。そして、インスタンスが不要になると、ARCは使用されているメモリを自動的に解放します。
インスタンスが不要になったかを判断するために 参照カウンタ という仕組みを利用します。 ARCは、クラスのインスタンスが参照しているプロパティ、定数、および変数の数を追跡し、インスタンスに対して1つでもアクティブな参照が存在する限りは、インスタンスを解放しません。1つも参照がなくなったときに初めてメモリを解放します。
次の例では、クラスのインスタンスを生成して3つの変数に代入しています。クラスは参照型なので3つとも同じ値を参照しています。
class Person {
var name: String
init(name: String) {
self.name = name
}
}
var person1: Person? = Person(name: "Taro")
// person1を代入
var person2 = person1
var person3 = person1
ここで、Person
クラスのインスタンスのメモリを解放するために nil を代入してみましょう。
person1 = nil
person1
に nil を代入しましたが、これではSwiftはメモリを開放しません。なぜなら、person2
と person3
がまだインスタンスの参照を保持しているからです。
参照カウンタは、1つも参照がなくなったときに初めてメモリを解放するという仕組みでした。なのでインスタンスのメモリを解放するには、残りの変数にも nil を代入する必要があります。
person2 = nil
person3 = nil
この段階で初めて全ての参照がなくなり、Swiftは自動的に使われていたメモリを解放します。インスタンスが解放されたかどうかは次節の デイニシャライザ
を使うと判断することができます。
# デイニシャライザ
デイニシャライザ
は、インスタンスが解放される直前に呼び出されます。
deinit
キーワードを使用してクラス内に定義します。
class Person {
var name: String
init(name: String) {
self.name = name
}
// デイニシャライザを定義する
deinit {
print("deinit!")
}
}
var person1: Person? = Person(name: "Taro")
var person2 = person1
var person3 = person1
インスタンスが解放されたか確認するために、変数全てにnilを代入してみましょう。
person1 = nil
person2 = nil
person3 = nil
/* 実行結果 */
// deinit!
全ての参照がなくなり、デイニシャライザが呼ばれたのが確認できます。
# 強参照の循環 | 循環参照
2つのインスタンス同士がお互いを参照しあっている関係を 強参照の循環 、または 循環参照 と呼びます。インスタンス同士が循環参照を起こしていると、メモリを解放することができません。なぜなら、お互いを参照しあっているので参照カウンタが0にならず、ずっと保持し続けてしまうからです。
次の例では、User
クラスと Profile
クラスを定義しています。お互いをプロパティとして持ちます。
class User {
var profile: Profile?
init() {}
deinit {
print("Userクラスのインスタンスが解放されました")
}
}
class Profile {
var user: User?
init() {}
deinit {
print("Profileクラスのインスタンスが解放されました")
}
}
それぞれインスタンスを生成してみましょう。
var user1: User? = User()
var profile1: Profile? = Profile()
この段階では、お互いのプロパティには nil が入っています。
では、生成したインスタンスをお互いのプロパティに代入してみましょう。
user1?.profile = profile1
profile1?.user = user1
プロパティにインスタンスを代入したことによって、下記の図のようにお互いを参照し合う関係になりました。
そのため、変数に nil を代入してもインスタンスの循環参照が残るのでメモリは解放されません。
user1 = nil
profile1 = nil
# 循環参照の解決
Swiftは、循環参照を解決するために2つの機能を提供しています。weak
と unonwned
です。
weak
と unonwned
を使うと強参照することなく、他のインスタンスを参照することができます。強参照されないと参照カウンタからカウントされません。そのため、インスタンスは循環参照をせずにお互いを参照することができます。
# 弱参照 | weak
weak
を使うと、ARCの参照カウンタにカウントされずに、参照をすることができます。
先ほどの例で、Profile
クラスの user
プロパティを弱参照させてみましょう。weak
キーワードは、プロパティの前に記述します。
class Profile {
// 弱参照させる
weak var user: User?
init() {}
deinit {
print("Profileクラスのインスタンスが解放されました")
}
}
これにより参照カウンタが1つになりました。そのため、変数に nil を代入すれば参照がなくなってメモリが解放されます。
user1 = nil
profile1 = nil
/* 実行結果 */
// Userクラスのインスタンスが解放されました
// Profileクラスのインスタンスが解放されました
デイニシャライザが呼ばれたので、ARCがインスタンスを開放したのが分かります。
# unowned
unowned
も同様に、ARCの参照カウンタにカウントされません。weakとの違いは、unowned
は nil にならない値を参照させるときに使います。
次の例では、User
クラスと Email
クラスを定義しています。User
クラスは、Email
クラスを持たない可能性がありますが、Email
クラスは常に User
クラスを必要としています。
そのため、User
クラスではプロパティをOptional型として持ち、Email
クラスでは必須にします。このときに、Email
クラスのプロパティを unowned
にすると参照カウンタにカウントされずに、User
クラスの参照がなくなった段階でメモリを解放することができます。
class User {
// Optional型として定義する
var email: Email?
init() {}
deinit {
print("Userクラスのインスタンスが解放されました")
}
}
class Email {
// unowned を記述して参照カウンタにカウントされないようにする
unowned var user: User
init(user: User) {
self.user = user
}
deinit {
print("Emailクラスのインスタンスが解放されました")
}
}
インスタンスを生成して変数に代入します。
var user1: User? = User()
user1?.email = Email(user: user1!)
参照関係は以下の図のようになります。
参照カウンタは1になるので、変数に nil を代入するとメモリを解放することができます。
user1 = nil
/* 実行結果 */
// Userクラスのインスタンスが解放されました
// Emailクラスのインスタンスが解放されました
プログラムの実行中に、プロパティの値が nil にならないことが明らかなときに unowned
を使うのがいいでしょう。
# Debug Memory Graph
Xcodeでは、参照関係を視覚的に確認することができます。
Xcodeの Debug Memory Graph をクリックするとインスタンス間の参照関係を見ることができます。