♻️

クロージャの中に書く[weak self] はじめからていねいに

2020/12/05に公開

はじめに

少し前に業務の中でクロージャを丁寧に実装する必要があり、その際に調べたものを文字残して、しっかりとまとめておこうと思いました。この記述が必要である理由を説明するには色々な知識が必要になりますが、この記事の結論を1行でまとめると

[weak self]と記述することでクロージャがselfを弱参照し、強参照による循環を防ぐ

になります。以下で順を追ってこのテーマについてまとめます。

強参照の循環

強参照とは

強参照 (strong reference)とはあるオブジェクトが他のオブジェクトを参照する際のデフォルトの参照方法です。Swiftでは、ARC (Auto Reference Counting) と呼ばれる仕組みで、オブジェクトが他のオブジェクトから参照されている数 (参照カウント)を記憶して、メモリ領域内のオブジェクトの管理を行います。オブジェクトの参照カウントが1以上の場合はそのオブジェクトは破棄されず、参照カウントが0になったとき、ガベージコレクションの要領でそのオブジェクトは破棄されます。

Swiftにおいて、あるオブジェクトが他のオブジェクトを参照した場合、参照した側は参照された側を強参照で参照したものとして処理されます。そして、ARCでは強参照で参照されたとき、そのオブジェクトの参照カウントは1として計上されます。

class Hoge {}
var h: Hoge? = Hoge() //(1)
h = nil //(2)

【Swift】クロージャの中に書く[weak self] はじめからていねいに.002

強参照の循環

他のプログラミング言語でも起こりうるように、Swiftでも参照型のオブジェクト同士では強参照による循環 (strong reference cycle)が起こり得ます。

class Hoge {
  var fuga: Fuga?
}
 
class Fuga {
 	var hoge: Hoge?
}

var h: Hoge? = Hoge()
var f: Fuga? = Fuga()

//(1)
h.fuga = f
f.hoge = h

//(2)
h = nil
f = nil

このような循環参照が発生した場合、Hoge()Fuga()のは参照カウントが1になったまま、各インスタンスへ変数からアクセスができなくない状態になったため、それぞれのインスタンスが解放されない状態となってしまっているのがわかります。

【Swift】クロージャの中に書く[weak self] はじめからていねいに.003

クロージャで起こりうる循環参照

上記のような循環参照はクロージャとあるクラスのインスタンスの間でも起こり得ます。この記事のタイトルにもなっている[weak self]はクロージャの中に記述するキャプチャリストの記法の1つです。以下の例を見てください。

class Hoge {
	private var closure: (() -> Void)?
	private var count = 0
	
	init() {
		closure = createClosure()
	}
	
	func createClosure() -> (() -> Void) {
		return { [self] in self.count += 1 }
	}
}

var h: Hoge? = Hoge() //(1)
h = nil //(2)

この例を実行した場合、hogeが生成された時点でイニシャライザ内部でcreateClosure()が呼び出され、クロージャ{ [self] in self.count += 1 }のインスタンスが生成され、プロパティclosureに格納され、self→クロージャへの参照が発生します。そして、このクロージャは内部でselfを参照しているため、クロージャ→selfへの強参照が発生します。すると、hと内部で生成されるクロージャのインスタンスは互いに強参照で参照しあっているため、参照の循環が発生していることがわかります。

【Swift】クロージャの中に書く[weak self] はじめからていねいに.004

弱参照による解決

上記の問題を解決するために、参照型のインスタンスを参照する際にSwiftでは強参照ではなく、弱参照(weak reference)という方法でインスタンスを参照する方法があります。弱参照による参照を受けた場合、ARCの参照カウントには含まれず、他のオブジェクトから参照されながらであってもメモリ領域を解放することができます。

これをクロージャのキャプチャリストに適用すると、記事のタイトルにある[weak self]の記法になります。これにより、先に説明したクロージャとインスタンスの強循環参照を無くし、インスタンスを解放することができるようになります。したがって、冒頭でも提示したように[weak self]とすることによって、[weak self]と記述することでクロージャがselfを弱参照し、強参照による循環を防ぐということになっています。

class Hoge {
	private var closure: (() -> Void)?
	private var count = 0
	
	init() {
		closure = createClosure()
	}
	
	func createClosure() -> (() -> Void) {
		return { [weak self] in self.count += 1 }
	}
}

var h: Hoge? = Hoge() //(1)
h = nil //(2)

【Swift】クロージャの中に書く[weak self] はじめからていねいに.005

参考

Discussion