オブジェクトの生涯

[親切すぎるiPhoneアプリ開発の本](https://www.amazon.co.jp/%E8%A6%AA%E5%88%87%E3%81%99%E3%81%8E%E3%82%8BiPhone%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E3%81%AE%E6%9C%AC-%E5%9C%8B%E5%B1%85%E8%B2%B4%E6%B5%A9-ebook/dp/B0177E99MQ) という本の序盤の「オブジェクトの生涯」というトピックで説明されていた内容です.

iOSアプリ開発の入門書ではあまり見られない内容だったので、自分なりに忘備録としてまとめました.

## Auto Reference Counting

通常、ARCという仕組みのおかげで、基本的にはオブジェクトの破棄をプログラマが気にする必要はない.

ARCとは、Auto Reference Countingの略で、オブジェクトが誰からも参照されなくなった時点で、自動的にメモリから破棄される仕組みである.

では、「オブジェクトが誰からも参照されなくなる時点」とはいつなのか？

```swift
func attack() {
    let slime = Monster(name: "スライム")
    print("\(slime)は攻撃した")
} // slimeはここで消える

attack() // 関数呼び出し
```

この場合、attack関数から戻る時がattack関数内で作成したMonsterが「誰からも参照されなくなる時点」.

関数の中で用意した定数slimeは、関数から戻る時にメモリから消える.(Monsterへの参照はなくなる)

## ARCが参照の数をカウント

ARCは、この過程をチェックして、オブジェクトに対する参照がなくなった時点でオブジェクトを破棄する.

もしattack関数内で作成したMonsterインスタンスを関数の外で生存させたい場合は、戻り値としてMonsterインスタンスの参照値を受け取る必要がある.

```swift
func attack() -> Monster {
    let slime = Monster(name: "スライム")
    print("\(slime)は攻撃した")
    
    return slime // 参照値を戻す
}

let anotherSlime = attack() // 関数内で作成したMonsterの参照値を受け取る
```
関数の終わりでslimeは消滅するが、参照値が戻り値として返され、新しくanotherSlimeによって参照される.

## デイニシャライザ
```swift
class Monster {
    var name: String
    init(name: String) {
        self.name = name
        print("\(self.name)はメモリに確保されました")
    }
    
    deinit {
        print("\(self.name)はメモリから消えます")
    }
}
```
デイニシャライザは、オブジェクトの破棄時に一度だけ呼ばれる処理.

オブジェクトがメモリから消失する時に、オブジェクトに関係したなんらかの処理をしたい時に利用.

```swift
var x: Monster! = Monster(name: "スライムx") // オブジェクト生成しに、xに参照を設定 *ここでは便宜上強制アンラップ
x = nil // xにnilを代入し、参照を解除(deinitが呼ばれる)
```
コンソール

```console
スライムxはメモリに確保されました
スライムxはメモリから消えます
x = nil
```
x = nil によって参照値がなくなり、それを察知したARCが、作成したスライムxオブジェクトを破棄している.

このように、参照を意図的になくすことでオブジェクトを破棄することも可能.

## オブジェクトの生涯
オブジェクトの生涯を順を追って確認.

```swift
class Monster {
    // 省略
}

func createMonster() {
    let y = Monster(name: "スライムy") // ②
    print("\(y.name)をxが参照") // ③
} // ④

createMonster() // ①
print("createMonster()から戻ってきました。")
```
① createMonster関数呼び出し
② 関数内でMonsterオブジェクト作成、参照
③ 関数内のprint実行
④ 関数から戻る時に、deinit実行

コンソール

```console
スライムyはメモリに確保されました
スライムyをxが参照
スライムyはメモリから消えます
createPerson()から戻ってきました。
```
createMonster関数から戻ることで、createMonster関数内の定数yが消える.
結果として参照が無くなり、オブジェクトも消える.

## 循環参照
違いのオブジェクトを参照しあう状態を「循環参照」と呼ぶ.

```swift
class Monster {
    
    var friend: Monster! // 別のMonsterを結びつける
    // 省略
}

var a: Monster! = Monster(name: "ゴーレムA")
var b: Monster! = Monster(name: "ゴーレムB")

a.friend = b
b.friend = a

a = nil
b = nil
```
コンソール

```console
ゴーレムAはメモリに確保されました
ゴーレムBはメモリに確保されました
```
最後にnilを設定しているにもかかわらず、"・・・はメモリから消えます"は出力されていない.

この理由は、オブジェクト間の循環した参照状態によるものである.

流れは次の通り.

1. aをnilにして、ゴーレムAオブジェクトへの参照を消しても、ゴーレムBオブジェクトからのfriendによる参照が残る.
 
2. ゴーレムAはメモリにとどまる.

3. その後のbへのnilの設定でも同様のことが起こっている.

4. つまり、ゴーレムAオブジェクトもゴーレムBオブジェクトもメモリ上に残っている.

これが循環参照という問題.


### 解決策
①

```swift
a.friend = b
b.friend = a
b.friend = nil // bのfriendプロパティにnilを代入
a = nil
b = nil
```
②

```swift
a.friend = b
b.friend = a
a.friend = nil // aのfriendプロパティにnilを代入
a = nil
b = nil
```
手間なので、weakキーワードを使った解決策がある.

### weakキーワード

```swift
class Monster {

    weak var friend: Monster! // weakキーワードを追加
    // 省略
}
```
weakキーワードはARCに対して、「このオブジェクト参照用のプロパティは、参照している数にはカウントしない」という制限を伝える.

その結果、aをnilにした時点でゴーレムAオブジェクトへの参照者は0になるので、ゴーレムAオブジェクトは破棄される.

また、ゴーレムAオブジェクトの破棄と同時に、ゴーレムB側のweak指定されたfriendプロパティにはnilが設定される. これもweak指定の特徴である.


## まとめ
プログラムが大きくなり、オブジェクトが複雑に絡み合う状況ではこのような知識も必要になる.

## 参考文献

この記事は以下の情報を参考にして執筆しました.

- [親切すぎるiPhoneアプリ開発の本](https://www.amazon.co.jp/%E8%A6%AA%E5%88%87%E3%81%99%E3%81%8E%E3%82%8BiPhone%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E3%81%AE%E6%9C%AC-%E5%9C%8B%E5%B1%85%E8%B2%B4%E6%B5%A9-ebook/dp/B0177E99MQ)