クラスのインスタンスへの参照には、強参照と弱参照の2種類があります。強参照は参照カウントを1つカウントアップし、弱参照はカウントアップしません。
デフォルトでは強参照となります。
弱参照
weakキーワードとともにプロパティを宣言すると、弱参照となります。
弱参照は循環参照の解消に用います。
循環参照とは
循環参照とは2つ(複数)のインスタンスが互いに強参照を持ち合う状態を指し、この状態では参照カウントが0にならずインスタンスが不要になっても開放されず、そのメモリは確保されたままとなってしまいます。
class Sam1 {
let val: String
var sam2: Sam2?
init(_ val: String) {
self.val = val
}
deinit { print("\(val) 開放") }
}
class Sam2 {
let val: String
var sam1: Sam1?
init(_ val: String) {
self.val = val
}
deinit { print("\(val) 開放") }
}
var ins1:Sam1? = Sam1("Sam1インスタンス") //「Sam1インスタンス」の参照カウント1
var ins2:Sam2? = Sam2("Sam2インスタンス") //「Sam2インスタンス」の参照カウント1
//-------互いに参照し合う---------------------------------------
//「Sam1インスタンス」のプロパティsam2が「Sam2 インスタンス」を参照
//ins2の参照カウント2
ins1!.sam2 = ins2
//「Sam2インスタンス」のプロパティsam1が「Sam1 インスタンス」を参照
//ins1の参照カウント2
ins2!.sam1 = ins1
//-------互いに参照し合う--------------------------------------
//変数からの参照を絶ってもそれぞれのクラス内のプロパティから参照し合っているので
//「Sam1インスタンス」「Sam2 インスタンス」どちらも参照カウントは0にはならない
ins1 = nil //「Sam1インスタンス」の参照カウント1
ins2 = nil //「Sam2インスタンス」の参照カウント1
弱参照による循環参照の対応
弱参照(weak参照)には以下のような特徴があります。
- 参照しても、参照カウントが増えない
- 参照先がnilになることがある
Optional型 - 定数で使用することはできない
使おうとしたらXcodeで以下のようなエラーが出ます
class Sam1 {
let val: String
var sam2: Sam2?
init(_ val: String) {
self.val = val
}
deinit { print("\(val) 開放") }
}
class Sam2 {
let val: String
//weak指定(弱参照)
weak var sam1: Sam1?
init(_ val: String) {
self.val = val
}
deinit { print("\(val) 開放") }
}
var ins1:Sam1? = Sam1("Sam1インスタンス") //「Sam1インスタンス」の参照カウント1
var ins2:Sam2? = Sam2("Sam2インスタンス") //「Sam2インスタンス」の参照カウント1
//-------互いに参照し合う---------------------------------------
//「Sam1インスタンス」のプロパティsam2が「Sam2インスタンス」を参照
//「Sam2インスタンス」の参照カウント2
ins1!.sam2 = ins2
//「Sam2インスタンス」のプロパティsam1が「Sam1インスタンス」を参照しているが
//弱参照なのでカウントはされない
//「Sam1インスタンス」の参照カウント1
ins2!.sam1 = ins1
//-------互いに参照し合う--------------------------------------
print(ins2!.sam1!) //Sam1
print(ins1!.sam2!) //Sam2
//「Sam1インスタンス」はこの時点で破棄されたので
//「Sam1インスタンス」のプロパティsam2が「Sam2 インスタンス」を参照は無くなるので
//「Sam2 インスタンス」の参照カウントは1になる
ins1 = nil //「Sam1インスタンス」の参照カウント0 出力:Sam1インスタンス 開放
//「Sam1インスタンス」は破棄されているので
//「Sam2インスタンス」のプロパティsam1はnilになる
print(ins2!.sam1) //nil
ins2 = nil //「Sam2インスタンス」の参照カウント0 出力:Sam2インスタンス 開放
クロージャによる循環参照
クロージャをプロパティとして持つ場合、クロージャプロパティからselfを参照すると以下のように循環参照になります。
class Sam {
var name: String = "yamada"
//クロージャプロパティ
var closure: (() -> ())!
init() {
// クロージャプロパティの中でself(インスタンス)をキャプチャ
//このタイミングでオブジェクト(インスタンス)はクロージャに所有される
closure = {
print(self.name)
}
print("保持")
}
deinit {
print("破棄")
}
}
var ins:Sam? = Sam()
ins?.closure()
ins = nil
//保持
//yamada
● 21行目
変数insがインスタンスを保有。
インスタンスを生成した段階でクロージャがインスタンスを保有し、インスタンスはクロージャを保有している、循環参照になっている。
この時点で、インスタンスへの参照カウントは2(変数insとクロージャ)。
「保持」を出力。
● 22行目
クロージャプロパティを実行。
「yamada」を出力
● 23行目
変数insにnilを代入したが、クロージャが保有を続けているのでインスタンスは解放されず「破棄」は出力されない。
この時点で、インスタンスへの参照カウントは1(クロージャ)
クロージャプロパティにもnilを代入する
クロージャプロパティにもnilを代入することでインスタンスは解放されます。
var ins:Sam? = Sam()
ins?.closure()
ins?.closure = nil //追加(クロージャプロパティにもnilを代入する)
ins = nil
//保持
//yamada
//破棄
クロージャからのselfの参照を弱参照にする。
以下のように6行目のようにクロージャからのselfの参照を弱参照にすることで循環参照を防ぐことができます。
class Sam {
var name: String = "yamada"
var closure: (() -> ())!
init() {
closure = { [weak self] in
print(self!.name)
}
print("保持")
}
deinit {
print("破棄")
}
}
var ins:Sam? = Sam()
ins!.closure()
ins = nil