MVVMを2パターン考えてみる（追記：MVPでしたすみません）

最近MVVMの記事がよく目につくような気がしたので、まとめておこうかなという記事です。

# MVVMとは
Model-View-ViewModelのことで、デザインアーキテクチャーの一つです。
最近では結構な人が名前くらいは、聞いたことあるのではないかと思います。

各役割について説明して行こうかと思います。
今回はrxを使用しないため、data bindingをしないので、少し違うかもしれませんが、ご了承ください。

## Model
主な役割は

- データの管理

です。

[お前らがModelと呼ぶアレをなんと呼ぶべきか。近辺の用語(EntityとかVOとかDTOとか)について整理しつつ考える](https://qiita.com/takasek/items/70ab5a61756ee620aee6)でも言われてる通り、定義が曖昧なところがあるので、Modelも2つのパターンがあるのかなと考えたので、紹介して行こうかなと思います。（間違いがあったりしましたらご指摘よろしくお願いします）

## View
主な役割は

- 表示

です。
iOSでは、UIViewControllerクラスもViewに入れられたりします。
そのため、ユーザーからのアクション（ボタンをタップするなど）を受け取るのはこちらになります。
ViewModelから来たデータを表示する・ユーザーからのアクションをViewModelに伝えます。

## ViewModel
主な役割は

- データの操作

です。
データの取得のために通信クラスのメソッドを呼んだり、通信で受け取ったデータをModelに渡したり、Modelから来たデータをViewに渡したりします。

# MVVM実装
自分が考える2つのパターンでの実装を紹介しようと思います。

## Model_Entity_Pattern
Modelがデータを管理するパターンです。
Entityは、データ構造体を表します。
Modelは、Entityを管理するようにします。

![MVVMER-2.jpg](https://qiita-image-store.s3.amazonaws.com/0/150615/0c777ef5-ed70-1fc7-d12e-942097933321.jpeg)

### Entity
Modelが管理するデータ構造体に変更があった時は、ViewModelへ伝えます。
データの構造体として、名前と年齢を持つEntityを作り、それをModelで管理します。

```Swift:Entity.swift
struct Entity {
	let name: String
	let age: Int
}

```

### Model
Modelが管理するデータに変更があった時に、ViewModelへ伝えるためにprotocolを実装します。

```Swift:Model.swift
protocol ModelToViewModel: class {
	func onChangeEntities(entities: [Entity])
}

class Model {
	
	var entityies: [Entity] = [] {
		didSet {
			viewModel?.onChangeEntities(entities: entities)
		}
	}
	
	weak var viewModel: ModelToViewModel?

}
```

### Repository
通信などを行うクラスとしてRepositoryを作成します。
今回は、仮でデータを作成してますが、ご了承ください。

```Swift:Repository.swift
protocol RepositoryToViewModel: class {
	func onGetEntities(entities: [Entity])
}

class Repository {
	weak var viewModel: RepositoryToViewModel?
	
	func getEntities() {
		// 通信などで取得する
		// 今回は仮で作成
		var entities = [Entity]()
		entities.append(Entity(name: "tarou", age: 25))
		entities.append(Entity(name: "zirou", age: 20))
		entities.append(Entity(name: "saburou", age: 18))
		viewModel?.onGetEntities(entities: entities)
	}
}
```

### ViewModel
ViewModelはModelとRepositoryを持ちます。
ModelとRepositoryからのdelegateもViewModelに実装します。
変更があったときに、Viewに伝えるためにprotocolを実装しておきます。

```Swift:ViewModel.swift
protocol ViewModelToView: class {
	func onChangeEntities()
}

class ViewModel {
	
	let model: Model = Model()
	let repository: Repository = Repository()
	
	weak var view: ViewModelToView?
	
	init() {
		model.viewModel = self
		repository.viewModel = self
	}
	
	func getEntities() {
		repository.getEntities()
	}
	
}

extension ViewModel: ModelToViewModel {
	
	// ModelからのDelegate
	func onChangeEntities(entities: [Entity]) {
		view?.onChangeEntities()
	}
	
}

extension ViewModel: RepositoryToViewModel {
	
	// RepositoryからのDelegate
	func onGetEntities(entities: [Entity]) {
		model.entities = entities
	}
	
}
```

### View
Viewは、UIViewControllerクラスを使用しました。
ViewModelクラスを持ちます。
Entityのnameを表示するためにMain.StoryBoardでUITableViewを実装しました。

```Swift:MVVMViewController.swift
class MVVMViewController: UIViewController {
	
	@IBOutlet weak var tableView: UITableView!
	
	let viewModel: ViewModel = ViewModel()
	
	override func viewDidLoad() {
		super.viewDidLoad()
		viewModel.getEntities()
	}
	
}

extension MVVMViewController: UITableViewDelegate, UITableViewDataSource {

	public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
		return viewModel.model.entities.count
	}

	public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
		let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "MVVMCell", for: indexPath)
		cell.textLabel?.text = viewModel.model.entities[indexPath.row].name
		return cell
	}

}

extension MVVMViewController: ViewModelToView {
	
	// ViewModelからのdelegate
	func onChangeEntities() {
		tableView.reloadData()
	}
	
}
```

### 思ったこと
Viewで、データを参照する時に、`viewModel.model.entities`となり、長くなってしまいました。

## Model_Pattern
ViewModelがデータを管理するパターンです。
Modelは、データ構造体を表します。
ViewModelが管理するデータ構造体に変更があった時は、Viewへ伝えます。

![MVVMER.jpg](https://qiita-image-store.s3.amazonaws.com/0/150615/b5f04c7c-2e90-c5f0-2ebe-b678e165c55d.jpeg)

コードでしていることは先ほどと同じです。実装をしているクラスが変わっているだけです。

### Model
```Swift:Model.swift
struct Model {
	let name: String
	let age: Int
}
```

### Repository
```Swift:Repository.swift
protocol RepositoryToViewModel: class {
	func onGetModels(models: [Model])
}

class Repository {
	weak var viewModel: RepositoryToViewModel?
	
	func getModels() {
		// 通信などで取得する
		// 今回は仮で作成
		var models = [Model]()
		models.append(Model(name: "tarou", age: 25))
		models.append(Model(name: "zirou", age: 20))
		models.append(Model(name: "saburou", age: 18))
		viewModel?.onGetModels(models: models)
	}
}
```

### ViewModel
ViewModelがデータの管理を行います。

```Swift:ViewModel.swift
protocol ViewModelToView : class {
	func onChangeModels()
}

class ViewModel {
	var models: [Model] = [] {
		didSet {
			view?.onChangeModels()
		}
	}
	
	let repository: Repository = Repository()

	weak var view: ViewModelToView?
	
	init() {
		repository.viewModel = self
	}

	func getModels() {
		repository.getModels()
	}

}

extension ViewModel: RepositoryToViewModel {
	
	// RepositoryからのDelegate
	func onGetModels(models: [Model]) {
		self.models = models
	}
	
}
```

### View
```Swift:MVVMViewController.swift
class MVVMViewController: UIViewController {
	
	@IBOutlet weak var tableView: UITableView!
	
	let viewModel: ViewModel = ViewModel()
	
	override func viewDidLoad() {
		super.viewDidLoad()
		viewModel.getModels()
	}
	
}

extension MVVMViewController: UITableViewDelegate, UITableViewDataSource {
	
	public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
		return viewModel.models.count
	}
	
	public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
		let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "MVVMCell", for: indexPath)
		cell.textLabel?.text = viewModel.models[indexPath.row].name
		return cell
	}
	
}

extension MVVMViewController: ViewModelToView {
	
	// ViewModelからのdelegate
	func onChangeModels() {
		tableView.reloadData()
	}
	
}
```

### 思ったこと
こちらの方がスッキリかけている気がする。（特にデータの参照`viewModel.models`）


# さいごに
MVVMについてまとめてる途中で2パターン行けると思い書いてみましたが、Model_Entity_Patternは微妙だったかなと思いました（笑）
データ参照が長くて毎回引き出すのはつらいなと...

```Swift:MVVMViewController.swift
extension MVVMViewController: ViewModelToView {
	
	// ViewModelからのdelegate
	func onChangeModels() {
		tableView.reloadData()
	}
	
}
```
`onChangeModels(models: [Model])`のように与えて使用できたらよかったのですが、`UITableViewDataSource`がありしかたなくデータの参照を書くことになりました。
今度はRxSwiftを使ってもっと簡単に書いていきたいですね（笑）

# 参考
https://qiita.com/mishimay/items/229913a1a0e980f45acc
https://qiita.com/takahirom/items/597c48ece57b4623cdee
https://qiita.com/marty-suzuki/items/5a4f680b10bb82501aa3
https://academy.realm.io/jp/posts/slug-max-alexander-mvvm-rxswift/

# 追記
ViewModelからデータを取得するときに、`viewModel.models.count`と直接触るのではなくて、ViewModelにデータ取得メソッドをおいた方がよかったですね。
Model_Patternでの修正してみました。
修正点

- modelsをprivate変数にした
- modelの個数を取得するメソッドの追加（`getModelCount`）
- modelのnameを取得するメソッドの追加（`getName()`）

```Swift:ViewModel.swift
class ViewModel {
	private var models: [Model] = [] {
		didSet {
			view?.onChangeModels()
		}
	}

	/* 省略 */	

	func getModelCount() -> Int {
		return models.count
	}
	
	func getName(index: Int) -> String {
		return models[index].name
	}
}
```

```Swift:MVVMViewController.swift
extension MVVMViewController: UITableViewDelegate, UITableViewDataSource {
	
	public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
		return viewModel.getModelCount()
	}
	
	public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
		let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "MVVMCell", for: indexPath)
		cell.textLabel?.text = viewModel.getName(index: indexPath.row)
		return cell
	}
	
}
```

# 追記_2
ViewModelがViewへの参照を持ってると、MVVMではなくてMVPではないかと教えていただいたので、追記しておきます。