循環参照についてさらっとまとめてみる

## はじめに
下記順番でアウトプットしていく。
- 循環参照とは？
- 循環参照の例
- 循環参照したときの修正案（さらっとまとめなので一部アウトプット）
- おまけ（ちょっと複雑な循環参照の可能性を示唆するコードの修正案）

## 循環参照とは？
循環参照とは、Swiftプログラムでクラス同士がお互いに`強い参照（強参照）`を持つときに発生する。
これにより、メモリが正しく解放されず、プログラムが予期しない挙動をする可能性がある。（アプリが落ちるとかね）

## 循環参照の例
例えば、クラスAがクラスBを保持し、同時にクラスBもクラスAを保持する場合、お互いが解放されない状態になる。下記コードは、最後の行の`instanceA = nil`と`instanceB = nil`を実行しても、クラスAとクラスBのインスタンスは解放されず、解放されたメッセージも表示されない。
```swift:Swift
class ClassA {
    var classB: ClassB? //　AでBを保持するため型を定義
    
    deinit {
        print("ClassAが解放されました")
    }
}

class ClassB {
    var classA: ClassA? // BでAを保持するため型を定義
    
    deinit {
        print("ClassBが解放されました")
    }
}

var instanceA: ClassA? = ClassA()
var instanceB: ClassB? = ClassB()

instanceA?.classB = instanceB //　AでBを保持する
instanceB?.classA = instanceA //　BでAを保持する

instanceA = nil // インスタンスは解放されない
instanceB = nil // インスタンスは解放されない
```

## 循環参照したときの修正案（さらっとまとめなので一部アウトプット）
循環参照を避けるためには、クラスAかクラスBのどちらかのプロパティに`weak修飾子`、つまり、`弱い参照（弱参照）`を使用する。または参照関係を見直す。
今回は、`弱い参照（弱参照）`の方で対応する。
```swift:Swift
class ClassA {
    var classB: ClassB?
    
    deinit {
        print("ClassAが解放されました")
    }
}

class ClassB {
    weak var classA: ClassA? // weak付けて弱参照にする
    
    deinit {
        print("ClassBが解放されました")
    }
}

var instanceA: ClassA? = ClassA()
var instanceB: ClassB? = ClassB()

instanceA?.classB = instanceB
instanceB?.classA = instanceA

// 循環参照が解消され、インスタンスが適切に解放されるようになる。
instanceA = nil
instanceB = nil

```

## おまけ（ちょっと複雑な循環参照の可能性を示唆するコードの修正案）
- 前提
  - MVVMを想定した上でSwiftUIを使用し、Combineを用いてAPI通信を行う。
  - ModelとViewModelの部分である。
  - `循環参照です！`がメインなのでデータ取得箇所などあまりよろしくないコードである。
``` swift
struct ItemModel: Codable {
    let id: Int
    let title: String
}

class ItemViewModel: ObservableObject {
    private let apiService = APIService()

    @Published var items: [ItemModel] = []
    @Published var errorMessage: String = ""
    private var cancellables = Set<AnyCancellable>()

    func fetchItems() {
        apiService.fetchItems()
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { [weak self] completion in
                switch completion {
                case .failure(let error):
                    self?.errorMessage = "An error occurred: \(error.localizedDescription)"
                case .finished:
                    break
                }
            },
            receiveValue: { [weak self] items in
                self?.items = items
                self?.errorMessage = ""
            })
            .store(in: &cancellables)
    }
}

class APIService {
    func fetchItems() -> AnyPublisher<[ItemModel], Error> {
        let url = URL(string: "https://api.example.com/items")! // 仮のAPIエンドポイント
        return URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: [ItemModel].self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}
```

## おわりに
間違い等ありましたらコメント欄にてご指摘ください！

## 参考記事
- [SwiftUIドキュメント](https://developer.apple.com/documentation/swiftui/)
- [【Swift】weakの役割とは？循環参照の意味とARCについて
](https://tech.amefure.com/swift-weak)
- [【Swift】ハマりがちな循環参照について
](https://qiita.com/1234224576/items/2241266f7d307cd25ba9)

## 開発環境
- Xcode-14.3
- Swift version 5.8