LoginSignup
3
4

More than 5 years have passed since last update.

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

Posted at

DIとは

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

ここで覚えて欲しいのは

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

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

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

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

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

サンプルコード

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
  }
}
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というパッケージを導入させて頂きました。そのため、少し記法が異なるかもしれません。ただ、使ってない方は便利なので皆さんも使って欲しいです。)

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

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

let cell = StationTableViewCell.dequeue(from: stationList, for: indexPath)

Instantiateを使うことでこれだけ簡潔に書くことができます。(上が元のコード、下がInstantiateを使ったとき)特にここはDIにも関係している部分なのでぜひ試して欲しいです。

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

完成形

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
    }
}
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
  }
}

という形になります。実際に確認したい場合はこちら

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

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4