UITableViewCellを例とするDI(dependency injection)開発[Swift]

## DIとは

DI(dependency injection)というのは簡単にいうと
**インスタンス変数（cellなどの容器）を呼び出すときに挿入するデータを呼び出してtestしやすいコード書こうぜ**
的なものだと認識しています。

ここで覚えて欲しいのは

 - `インスタンス変数（cellなどの容器）`と同時に`挿入する情報`も呼び出している
 - testしやすいコードである

の２つです。
これだと一見メリットしかないように見えますが強いてデメリットを挙げるとすると、

 - storyboardに直接書く記述には使えない(必要がない)
 - リリース前とかのとりあえずリリースしないといけない時期には必要性が薄い

あとは１つのファイルで管理するというよりも複数のファイルで可読性よく管理するので何個ものファイルを行き来するのがめんどい人とかはそれもデメリットになるかもです。
(小さいプロジェクトにおいてはめんどいだけかもしれませんし。)

*ここからは実際にコードで説明していきます。*

## サンプルコード

```swift:viewController.swift
import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

  @IBOutlet weak var stationList: UITableView!
  var stations:[Station] = [Station]()
  
  
  override func viewDidLoad() {
    super.viewDidLoad()
    stationList.dataSource = self
    stationList.delegate = self
    stationList.register(UINib(nibName: "StationTableViewCell", bundle: nil), forCellReuseIdentifier: "StationTableViewCell")
    self.setupStations()
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
  }
  
  func setupStations() {
    stations = [Station(name: "飯田橋", prefecture: "東京都新宿区"), Station(name: "九段下", prefecture: "東京都千代田区"), Station(name: "御茶ノ水", prefecture: "東京都文京区") ];
  }
  
  func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
  }
  
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return stations.count
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "StationTableViewCell", for: indexPath ) as! StationTableViewCell
    
    cell.setCell(station: stations[indexPath.row])
    
    return cell
  }
}
```
```swift:stationTableViewCell.swift
import UIKit

class StationTableViewCell: UITableViewCell {

  @IBOutlet weak var name: UILabel!
  @IBOutlet weak var prefecture: UILabel!
  
  override func awakeFromNib() {
        super.awakeFromNib()
    }
  
  func setCell(station: Station) {
    self.name.text = station.name as String
    self.prefecture.text = station.prefecture as String
  }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
    
}
```
今上に載せたのは所謂cutomCellを作成しようとしたときに出てくる普通のコードです。

今回はこれをいじることでDIのコードを作っていきたいと思います。

（今回サンプルコードを作成するにあたって[Instantiate](https://github.com/tarunon/Instantiate)というパッケージを導入させて頂きました。そのため、少し記法が異なるかもしれません。ただ、使ってない方は便利なので皆さんも使って欲しいです。）

```swift:viewController.swift
stationList.register(UINib(nibName: "StationTableViewCell", bundle: nil), forCellReuseIdentifier: "StationTableViewCell")

stationList.registerNib(type: StationTableViewCell.self)
```
```swift:viewController.swift
let cell = tableView.dequeueReusableCell(withIdentifier: "StationTableViewCell", for: indexPath ) as! StationTableViewCell

let cell = StationTableViewCell.dequeue(from: stationList, for: indexPath)
```
[Instantiate](https://github.com/tarunon/Instantiate)を使うことでこれだけ簡潔に書くことができます。(上が元のコード、下が[Instantiate](https://github.com/tarunon/Instantiate)を使ったとき)特にここはDIにも関係している部分なのでぜひ試して欲しいです。

( 注意点：これを行うには`StationTableViewCell`が`Reusable`と`NibType`を呼んでいる必要があります。最後に乗っているサンプルコードを読んでみてください。)

## 完成形
```swift:viewController.swift
import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var stationList: UITableView!
    var stations:[Station] = [Station]()

    override func viewDidLoad() {
        super.viewDidLoad()
        stationList.dataSource = self
        stationList.delegate = self
        stationList.registerNib(type: StationTableViewCell.self)
        self.setupStations()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func setupStations() {
        stations = [Station(name: "飯田橋", prefecture: "東京都新宿区"), Station(name: "九段下", prefecture: "東京都千代田区"), Station(name: "御茶ノ水", prefecture: "東京都文京区") ];
    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return stations.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = StationTableViewCell.dequeue(from: stationList, for: indexPath, with: stations[indexPath.row])
        return cell
    }
}
```
```swift:StationTableViewCell.swift
import UIKit
import Instantiate
import InstantiateStandard

class StationTableViewCell: UITableViewCell {

    @IBOutlet weak var name: UILabel!
    @IBOutlet weak var prefecture: UILabel!
  
    override func awakeFromNib() {
          super.awakeFromNib()
    }
  
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
}

extension StationTableViewCell: Reusable & NibType {
  typealias Dependency = Station
  
  func inject(_ dependency: Dependency) {
    let station = dependency
    name.text = station.name
    prefecture.text = station.prefecture
  }
}
```
という形になります。実際に確認したい場合は[こちら](https://github.com/takumaosada/customCellTutorial/tree/feature/DI)

```swift:viewController.swift
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = StationTableViewCell.dequeue(from: stationList, for: indexPath, with: stations[indexPath.row])
        return cell
    }
```
に注目するとcellを定義した後に`setCell`というfunctionの記述が省略されています。これが

- `インスタンス変数（cellなどの容器）`と同時に`挿入する情報`も呼び出している

に該当している部分なのです。`with:`以降で`挿入する情報`も呼び出しているのです！！！

ViewController内部の記述が少なくなりスッキリ見えますね。

最後にこちらからコードの全体像を確認できるのでみてみてください。
https://github.com/takumaosada/customCellTutorial/tree/feature/DI

### 関連性があるもの
https://github.com/takumaosada/customCellTutorial/tree/feature/DI (今回のサンプルコード)
https://github.com/Swinject/Swinject
https://github.com/tarunon/Instantiate


### 参考にした記事
[1分で分かる Dependency Injection in Swift](https://qiita.com/kusumotoa/items/c924d5c67282fd11f664)