[Swift] すっきりまとまる！DIを活用したCellセットアップ方法

# はじめに

UITableViewCellやUICollectionViewCellを定義する際にCellとViewController間の依存関係を解消するための方法としてCellのセットアップ方法についてご紹介します。

※この内容は他のQiitaの記事やインターン先で学んだことも含まれています。

# DI(Dependency Injection)とは？

まず、DIについて説明します。
他の記事でも多くDIの説明をしているので、複数の記事を比べながら理解していくと良さそうです。

依存性をなくすため、動的に必要なプロパティを注入するように定義する考え方

外から注入して、動的に値を変えられるようにするといったところでしょうか。

#### メリット

- 必要なプロパティを漏らすことなく、コンポーネントの生成が可能
- 必要なプロパティが明示的になることにより、アプリの保守性の向上

#### デメリット

- 定義する回数やファイル数の増加
- 期限が決まっている早急なアプリ開発には不向き

# 従来のCell定義

次に，従来のCellの定義について説明します．
よくサイトでCellの定義の仕方を探していくと，以下のようなコードが出てくると思います．

```ViewController.swift
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "FormPostTableViewCell", for: indexPath) as! FormPostTableViewCel
        cell.testLabel.text = "しつかり"
        return cell
    }
```

しかし、この問題点として、以下が考えられます．

- CellとViewController間が密結合（CellのLabelプロパティを呼び出してたり）
- 変数を一つずつ呼び出さないといけない。
- 非同期で処理をする場合、その都度クロージャを定義する必要がある。

そこで、上記の問題を解決するためにDIを活用したCustomCellの定義方法を紹介します。

まずは、次のコードを一通りご覧ください。

# 実際のコード

```FormPostTableViewCell.swift
class FormPostTableViewCell: UITableViewCell {
    @IBOutlet weak var postButton: UIButton! {
        didSet {
            postButton.addTarget(self, action: #selector(postChara), for: .touchUpInside)
        }
    }
    
    struct Dependency {
        enum EventType {
            case tapped
        }
        
        var isEnabled: Bool
        var eventType: (EventType) -> Void
    }
    
    private var dependency: Dependency?
    
    func inject(with dependency: Dependency) {
        self.dependency = dependency
        self.switchButton = dependency.isEnabled
    }
    
    @objc 
    private func postChara() {
        dependency?.event(.tap)
    }
}

```

```ViewController.swift
extension CharaPostViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return model.tableSection.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        switch model.tableSection[indexPath.row] {
        case .imageComponent:// 省略
        case .textComponent:// 省略
        case .buttonComponent:
            let cell = tableView.dequeueReusableCell(withIdentifier: "FormPostTableViewCell", for: indexPath) as! FormPostTableViewCell
            let component = FormPostTableViewCell.Dependency(isEnabled: model.parameter) { eventType in
                switch eventType {
                case .tap:// 省略
                  // 何かしらの処理
                }
            }
            cell.inject(with: component)
            return cell
        }
    }
    
}
```

# コード解説

それでは、コードの解説をしていきます。

## FormPostTableViewCell.swiftの解説

構造体にはenumの`EventType`と`isEnabled`プロパティ、eventTypeのクロージャを定義します。
これにより、外から注入できるようなプロパティの集合を定義することができます。

```.swift
    struct Dependency {
        enum EventType {
            case tapped
        }
        
        var isEnabled: Bool
        var eventType: (EventType) -> Void
    }
```

次に、外から注入するための入り口を作ります。
`inject`メソッドを作成し、引数は`Dependency`の構造体にします。

そうすると、外から代入したため`dependency`から先ほど`Dependency`で定義したプロパティが参照できます。

```.swift
    private var dependency: Dependency?
    
    func inject(with dependency: Dependency) {
        self.dependency = dependency
        self.switchButton = dependency.isEnabled
    }
```

また、問題定義にもあったクロージャも以下のように定義することができます。
`eventType`プロパティを参照し、`.tap`を設定することでtap時のアクションをVCに送ることができます。（VCがどのようになっているかは後述しています。）

```.swift
    @objc 
    private func postChara() {
        dependency?.eventType(.tap)
    }
```

## ViewController.swiftの解説

VCで定義する際は以下のように定義します。
`FormPostTableViewCell.Dependency`とすることで、必要なプロパティとCellのアクションを一括で定義することが可能になります。

```.swift

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        switch model.tableSection[indexPath.row] {
        case .imageComponent:// 省略
        case .textComponent:// 省略
        case .buttonComponent:
            let cell = tableView.dequeueReusableCell(withIdentifier: "FormPostTableViewCell", for: indexPath) as! FormPostTableViewCell
            let component = FormPostTableViewCell.Dependency(isEnabled: model.parameter) { eventType in
                switch eventType {
                case .tap:// 省略
                  // 何かしらの処理
                }
            }
            cell.inject(with: component)
            return cell
        }
    }

```

# 最後に

本記事では、DIを活用したCellセットアップ方法について紹介しました。
まだまだ改善方法はあると思うので、何かあればご意見やご指摘をいただければと思います！

# 参考記事
https://qiita.com/kusumotoa/items/c924d5c67282fd11f664#initializer-injection