【アーキテクチャ】MVVM【Swift】


# MVVMとは？
プログラムを3つの要素、Model、View、ViewModel に分割
各要素は、単方向に依存している View -> ViewModel -> Model
MVVMは、あくまでUI周り構成について触れているだけであって、Modelの中身については、各自で考える必要がある

# サンプルコードと簡単な概念
文章だとイメージつかないので実際にソースコード使って書いてみました。

# Model

ViewとViewModelがやること以外全て
データ本体、加工、取得、保存などは、全てMdoelに記載

```
import Foundation

enum Gender {
    case male, female, unspecified
}

struct Person {
    let name: String
    let birthDate: Date?
    let middleName: String?
    let address: String?
    let gender: Gender
    
    var username = "Hachi"
    
    init(name: String,
         birthDate: Date? = nil,
         middleName: String? = nil,
         address: String? = nil,
         gender: Gender = .unspecified) {
        self.name = name
        self.birthDate = birthDate
        self.middleName = middleName
        self.address = address
        self.gender = gender
    }
}
```

# ViewModel

UIに描画するのに必要な情報を準備、保持する

```
import UIKit

struct PersonFollowingTableViewCellViewModel {
    let name: String
    let username: String
    let currentlyFollowing: Bool
    let image: UIImage?
    
    init(with model: Person) {
        name = model.name
        username = model.username
        currentlyFollowing = false
        image = UIImage(systemName: "person")
    }
}
```

# View
ViewModelの情報を使用してUIを描画

```
import UIKit

class PersonFollowingTableViewCell: UITableViewCell {
    
    static let identifier = "PersonFollowingTableViewCell"
    
    private let userImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.clipsToBounds = true
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()
    
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.textColor = .label
        return label
    }()
    
    private let usernameLabel: UILabel = {
        let label = UILabel()
        label.textColor = .secondaryLabel
        return label
    }()
    
    private let button: UIButton = {
        let button = UIButton()
        return button
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.addSubview(userImageView)
        contentView.addSubview(nameLabel)
        contentView.addSubview(usernameLabel)
        contentView.addSubview(button)
        contentView.clipsToBounds = true
    }
    
    required init?(coder: NSCoder) {
        fatalError()
    }
    
    func configure(with viewModel: PersonFollowingTableViewCellViewModel) {
        nameLabel.text = viewModel.name
        usernameLabel.text = viewModel.username
        userImageView.image = viewModel.image
        
        if viewModel.currentlyFollowing {
            button.setTitle("unFollow", for: .normal)
            button.setTitleColor(.black, for: .normal)
            button.layer.borderWidth = 1
            button.layer.borderColor = UIColor.black.cgColor
        } else {
            button.setTitle("Follow", for: .normal)
            button.setTitleColor(.white, for: .normal)
            button.backgroundColor = .link
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let imageWidth = contentView.frame.size.height - 10
        
        userImageView.frame = CGRect(
            x: 5,
            y: 5,
            width: imageWidth,
            height: imageWidth
        )
        nameLabel.frame = CGRect(
            x: imageWidth + 10,
            y: 0,
            width: contentView.frame.size.width - imageWidth,
            height: contentView.frame.size.height / 2
        )
        usernameLabel.frame = CGRect(
            x: imageWidth + 10,
            y: contentView.frame.size.height / 2,
            width: contentView.frame.size.width - imageWidth,
            height: contentView.frame.size.height / 2
        )
        button.frame = CGRect(
            x: contentView.frame.size.width - 120,
            y: 10,
            width: 110,
            height: contentView.frame.size.height - 20)
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
    }

}
```

```
import UIKit

class ViewController: UIViewController, UITableViewDataSource {
    
    private var models = [Person]()
    
    private let tableView: UITableView = {
        let table = UITableView()
        table.register(PersonFollowingTableViewCell.self, forCellReuseIdentifier: PersonFollowingTableViewCell.identifier)
        return table
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        configureModels()
        view.addSubview(tableView)
        tableView.dataSource = self
        tableView.frame = view.bounds
    }
    
    private func configureModels() {
        let names = [
            "Hanako", "Momo", "Leo", "Kotaro", "Malon"
        ]
        
        for name in names {
            models.append(Person(name: name))
        }
        
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return models.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let model = models[indexPath.row]
        guard let cell = tableView.dequeueReusableCell(
            withIdentifier: PersonFollowingTableViewCell.identifier,
            for: indexPath
        ) as? PersonFollowingTableViewCell else {
            return UITableViewCell()
        }
        cell.configure(with: PersonFollowingTableViewCellViewModel(with: model))
        return cell
    }

}

```

# ViewModelとModelはどう違うのか？

ViewModelはViewに表示する情報を保持しますが、ModelはViewには表示しないけどアプリに必要なデータ構造、データ処理、そのほかメソッド・コマンド・プロパティなどを保持します。
ViewModelはViewとModelを繋ぐ役割をしており、ViewModelではViewに表示させるデータを保持します。

# MVVMのメリット
- 可読性の向上
- テストの容易性
- Viewへの更新通知、指示の部部分のコードが不要。

# MVVMのデメリット
- 小規模、少人数のプロジェクトではコストに対してリターンが少ない。

# 参考
https://teratail.com/questions/216820#:~:text=%E3%81%96%E3%81%A3%E3%81%8F%E3%82%8A%E8%AA%AC%E6%98%8E%E3%81%99%E3%82%8B%E3%81%A8Model%E3%82%AF%E3%83%A9%E3%82%B9,%E3%82%92%E3%81%99%E3%82%8B%E3%81%AE%E3%81%BF%E3%81%AB%E7%95%99%E3%82%81%E3%82%8B%E3%80%82
https://qiita.com/ebi-toro/items/046c9a43b15b9e376198
https://www.youtube.com/watch?v=qzXJckVxE4w
https://laptrinhx.com/iosapuriakitekucha-bi-jiao-jian-tao-cocoa-mvc-mvvm-mvp-cleanarchtecture-3832902000/