DIとは
DI(dependency injection)というのは簡単にいうと
インスタンス変数(cellなどの容器)を呼び出すときに挿入するデータを呼び出してtestしやすいコード書こうぜ
的なものだと認識しています。
ここで覚えて欲しいのは
-
インスタンス変数(cellなどの容器)
と同時に挿入する情報
も呼び出している - testしやすいコードである
の2つです。
これだと一見メリットしかないように見えますが強いてデメリットを挙げるとすると、
- storyboardに直接書く記述には使えない(必要がない)
- リリース前とかのとりあえずリリースしないといけない時期には必要性が薄い
あとは1つのファイルで管理するというよりも複数のファイルで可読性よく管理するので何個ものファイルを行き来するのがめんどい人とかはそれもデメリットになるかもです。
(小さいプロジェクトにおいてはめんどいだけかもしれませんし。)
ここからは実際にコードで説明していきます。
サンプルコード
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
}
}
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というパッケージを導入させて頂きました。そのため、少し記法が異なるかもしれません。ただ、使ってない方は便利なので皆さんも使って欲しいです。)
stationList.register(UINib(nibName: "StationTableViewCell", bundle: nil), forCellReuseIdentifier: "StationTableViewCell")
stationList.registerNib(type: StationTableViewCell.self)
let cell = tableView.dequeueReusableCell(withIdentifier: "StationTableViewCell", for: indexPath ) as! StationTableViewCell
let cell = StationTableViewCell.dequeue(from: stationList, for: indexPath)
Instantiateを使うことでこれだけ簡潔に書くことができます。(上が元のコード、下がInstantiateを使ったとき)特にここはDIにも関係している部分なのでぜひ試して欲しいです。
( 注意点:これを行うにはStationTableViewCell
がReusable
とNibType
を呼んでいる必要があります。最後に乗っているサンプルコードを読んでみてください。)
完成形
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
}
}
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
}
}
という形になります。実際に確認したい場合はこちら
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