クロージャの中に書く[weak self]についてまとめてみた

自分の備忘録。

#はじめに
ある日、メンターさんにコードレビューして頂いている時に言われた「`[weak self]がないと循環参照してメモリリークする`」という一言からこの記事を書こうと思いました。

#結論から
色々、調べていくと`[weak self]と書くことでクロージャがselfを弱参照し、クロージャとselfの循環参照を防ぐことができる`そうです。

この時点では???です。

そして、新たに`弱参照`という単語が出てきました。
`循環参照`を知るには、まだ時間が掛かりそうですね。

調べてみましょう。

#強参照
調べていくと、まずは弱参照と対にある`強参照`を知る必要がありそうですね。

強参照とは`あるオブジェクトが他のオブジェクトを参照する際のデフォルトの参照方法`です。

swiftでは`ARC(Auto Reference Counting)`という仕組みを用いて、オブジェクトが他のオブジェクトから参照されている数(`参照カウント`)を記憶してメモリ領域内のオブジェクト管理を行います。

そして、メモリに確保されたインスタンスやプロパティが必要でなくなったら
ARCによって自動的に破棄されます。
[Swiftのメモリ管理【weakやunownedをわかりやすく解説】](https://rakusui.org/swift_arc/)

```swift
class A {}

// 参照カウントが１になる
var a: A? = A() // 1

// 参照カウントが0になり解放される
a = nil // 0
```
ですが、この強参照という方法は`循環参照`という問題を引き起こしてしまいます。

#循環参照
参照型のオブジェクト同士では`強参照による循環(strong reference cycle)`が起こってしまいます。

コードで表すとこんな感じ↓

```swift
class A {
    var b: B?
}

class B {
    var a: A?
}

// クラスAとBのインスタンス
var a: A? = A() // Aクラスの参照カウント1
var b: B? = B() // Bクラスの参照カウント1

// --------互いに参照し合う--------

b?.a = a // Aクラスの参照カウント2
a?.b = b // Bクラスの参照カウント2

// --------互いに参照し合う--------

// たとえ各インスタンスが解放されたとしても、それぞれのクラス内のプロパティから参照し合っているのでどちらも参照カウントは1である
a = nil // Aクラスの参照カウント1
b = nil // Bクラスの参照カウント1
```
これが`循環参照`です。

つまり、`お互いに参照し合って、どちらも解放されずにそのままメモリ内に残り続けてしまうという状態`です。

これらが原因で`メモリリーク`が起きてしまうんですね。
メモリリークの説明はこちらの記事が凄く分かりやすく解説してくれていますので参考にしてみて下さい↓
[Swiftのメモリリークについてまとめてみた](https://qiita.com/ryu1_f/items/201275f9ac3af1ec9e64)

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

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

var a: A? = A() // Aクラスの参照カウント2、closureの参照カウント1
a = nil // Aクラスの参照カウント1

// selfとクロージャが参照し合っているので循環参照が起こってしまっている
```
`Aクラス`を生成した段階で、Aクラスのイニシャライザ内部で`createClosure()`メソッドが呼び出されて
`{ [self] in self.count += 1 }`のインスタンスが生成されます。

そして、closureプロパティに代入され

`self(Aクラス) > クロージャ({ [self] in self.count += 1 })への参照`

が発生。

更に、このクロージャは内部でselfを参照しているので

`クロージャ({ [self] in self.count += 1 }) > self(Aクラス)への参照`

も発生してしまう。

そうなると、たとえaインスタンスが解放されたとしても`self(Aクラス)`と`クロージャ({ [self] in self.count += 1 })`がお互いに参照し合うので参照の循環が起きてしまい`循環参照`の原因となる訳です。

#弱参照
この循環参照を解決するのが`弱参照(weak reference)`です。

これは`ARCの参照カウントには含まれず、他のオブジェクトから参照されながらであってもメモリを解放することができる`という参照方法です。

じゃあ、どうやって弱参照するのか？というと`[weak self]`をクロージャ内に記述します。

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

var a: A? = A() // Aクラスの参照カウント1、closureの参照カウント1
a = nil // Aクラスの参照カウント0

// クロージャ({ [weak self] in self?.count += 1 }が弱参照によってself(Aクラス)との循環参照が起こらない！
```
クロージャの中に`[weak self]`と記述することによって`弱参照`になり、Aクラスの参照がカウントされなくなりました。

これで、先ほどみたいにお互いが参照し合わないので`循環参照`が起こりません。

因みに[weak self]と書いている部分は`キャプチャリスト`と呼ばれます。
こちらの記事を参考にしてみて下さい↓
[【Swift】キャプチャ・リスト](https://qiita.com/chino_tweet/items/6335c660a75d6da30bc4)
#おわりに
冒頭で述べた「`[weak self]がないと循環参照してメモリーリークする`」という意味が見えてきましたね。
実験的にやってみたい方はこの記事が参考になりますよ↓
[SwiftやiOSの便利だけど忘れそうな小ネタ集](https://qiita.com/sakiyamaK/items/5a5d99749d8a0fa892de#weak)

後で自分も実験してみて循環参照を肌で感じたいと思います。
#参考資料
[Swiftでなんで[weak self]するのか？](https://speakerdeck.com/oyuk/swiftdenande-weak-self-surufalseka?slide=17)
[クロージャの中に書く[weak self] はじめからていねいに](https://zenn.dev/mhackit/articles/a0b1c6e780c3c6aabe45)
[【swift】弱参照と循環参照への対応について](https://scleapt.com/swift_circulation_reference/)
[【swift】ハマりがちな循環参照について](https://qiita.com/1234224576/items/2241266f7d307cd25ba9#3%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%A3%E3%81%A7%E7%99%BA%E7%94%9F%E3%81%99%E3%82%8B%E5%BE%AA%E7%92%B0%E5%8F%82%E7%85%A7)