循環参照の理解から対策まで


循環参照の理解から対策までを記述しました。

## ステップ1:メモリの仕組みを理解する
SwiftのメモリはARC(Automatic Reference Counting)と呼ばれる仕組みで管理されています。
ARCはオブジェクトを参照する数を数え、その数がゼロになると、そのオブジェクトは不要と見なされ、メモリから解放される。
これによって、プログラムが無駄なメモリを使わずに効率的に動作できます。

#### メモリの確保
ARCがメモリを確保するのは`クラスのインスタンスを生成`したタイミングである。
そこから`変数に格納されるとそのメモリ(インスタンス)の参照という形で参照数がカウントされる`。

以下、ゲームに例えてコードで実装してみる。

```swift
// キャラクタークラス
class Character {
    var name: String
    init(name: String) {
        self.name = name
    }
}
// メモリ上に勇者(オブジェクト)を生成。変数に格納されため、キャラクタークラスの参照数は1
var character1:Character? = Character(name: "hero")
// 参照数は1
```

再度、キャラクタークラスをインスタンス化して別の変数に格納する。参照カウントは2となります。

```swift
// メモリ上にスライム(オブジェクト)を生成。変数に格納されため、キャラクタークラスの参照数は2
var character2:Character? = Character(name: "slime")
// 参照数は2

```
Characterクラスは２つの変数から参照されている。
Swiftのクラスは参照型でデータを保持しているのでこの参照カウントはクラスに対してのみ行われます。


:::note info

おすすめ記事：https://qiita.com/yoshidasss/items/fabc5d6cc63344c6ab77

:::

#### メモリの解放

インスタンスに対して1つでも参照しているものが存在する限りは、インスタンスを解放しない。インスタンスへお参照がゼロになったときに初めてメモリを解放します。

```swift
// character1にnilを代入したが、これではSwiftはメモリを開放しない
var character1:Character? = nil

// character2にnilを代入したことにより、参照がゼロになり初めてメモリを解放する
var character2:Character? = nil
```
上記の説明でswiftのメモリの仕組みが理解できたので、次は本題の「循環参照」について考えます。



## ステップ2:循環参照とは

2つのインスタンス同士がお互いを参照しあっている関係を循環参照と呼ぶ。互いを参照しあっているので参照カウンタがゼロにならず、メモリを解放することができない。

本来もう使用することはないデータがメモリ内に永続的に残っていてしまい、不要なメモリが解放されない状態が続いてしまっている。

以下のコードで説明します。

`Person`クラスと`Pet`クラスがあります。
`Person`はペットを所有する(petプロパティ)、また、`Pet`には飼い主がいる(ownerプロパティ)。
```swift
//　人間
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


```

上記により以下の関係性が設定されます。
プロパティにインスタンスを代入したことによって、下記の図のようにお互いを参照し合う関係になった。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3671424/6ec65312-1285-61f0-afa9-cf14b1ba95e9.png" width="70%">

変数`john`にnilを代入して、Swiftのメモリを開放してみるが・・・
```swift
// nilを格納しても参照は0にならない
john = nil
```
この場合変数`john`にnilを格納(Personインスタンスの参照を解放)しても、PersonインスタンスはPetインスタンスのプロパティに紐づいているため参照は0にならない。
Personインスタンスは、`dog?.owner = john`により、犬の飼い主として設定されている状態。(参照されている)

さらにこのまま変数`dog`にもnilを格納する。

それぞれのインスタンスを指す変数はもう`nil`が代入されているので、これらのインスタンスにアクセスする手段は無く宙ぶらりんの状態で不要にメモリを占有しメモリリークしています。(循環参照)

```swift
// nilを格納しても参照は0にならない
dog = nil
```

上記により以下の関係性が設定されます。
インスタンスの循環参照が残り続けてしまいます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3671424/22625b29-c818-5653-3fc5-3cd6725ed931.png" width="100%">

## ステップ3:循環参照の対策

循環参照の対策について説明します。
キーワードになるのが**弱参照、強参照**になります。

### 強参照:storong
ARCではインスタンスとデータの参照は基本的に「強参照」と呼ばれる強い参照で紐付けられ参照カウントが増やされます。そしてそのデータに対して強参照が残っている(カウントが0でない)限りメモリが解放されることはなく、また別インスタンスからの強参照により循環参照などが発生する原因になります。

### 弱参照:weak

強参照とは違い、`弱参照は参照カウントを増やしません`。
参照しているオブジェクトが解放されると、弱参照は`自動的にnil`になります。

このweakキーワードをプロパティ宣言の前に付与することで`参照の強さ`を変更することができます。  

以下、コードで検証してみましょう。

`Pet`クラスのプロパティにweakキーワードを付与します。
また、値としてnilを許容する必要があるので型はオプショナル型である必要があります。

ついでにデイニシャライザが起動していることを確認するため、print文も追加しておきます。
※デイニシャライザは、インスタンスが解放される直前に呼び出されます。

```swift
//　人間
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の参照カウンタにカウントされず`に、参照をすることができます。

```swift
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になりました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3671424/65b82bee-e49f-a74b-fe50-e8d4d8afeb7b.png"="50%">


ここで、変数`john`にnilを代入すると、Personクラスのインスタンスが解放されます。


```swift
print("------------------------------")
john = nil
print("------------------------------")

// ------------------------------
// Personクラスのインスタンスが解放されました
// ------------------------------
```

上記により以下の関係性が設定されます。
`Person`クラスのインスタンスは最初の状態で参照カウントが1だったため、変数`john`にnilを代入した結果、`Person`クラスのインスタンスの参照カウントが0になり、`Person`クラスのインスタンスは破棄されます。

そして、`Pet`クラスのインスタンスへの参照カウントが1減って、1になります。また、`Pet`クラスのインスタンスが参照する`Person`クラスのインスタンスへのプロパティは弱参照となっているため、**`Person`クラスのインスタンスの破棄に伴いnilが設定されます**。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3671424/eaf2c38d-7ae8-8cd5-55de-be491c184dce.png"="50%">


この後は、変数`dog`にnilを代入することで、`Pet`クラスのインスタンスの参照カウントが0になり破棄されます。これでメモリリークすることはなくなりました。

```swift
print("------------------------------")
dog = nil
print("------------------------------")

// ------------------------------
// Petクラスのインスタンスが解放されました
// ------------------------------
```

## 参考
https://tech.amefure.com/swift-weak

https://swift.codelly.dev/guide/%E3%83%A1%E3%83%A2%E3%83%AA%E7%AE%A1%E7%90%86/#%E5%BE%AA%E7%92%B0%E5%8F%82%E7%85%A7%E3%81%AE%E8%A7%A3%E6%B1%BA

https://tea-leaves.jp/swift/content/arc