Upgrade to Pro — share decks privately, control downloads, hide ads and more …

MVVM overview

Ee4f2266abb0268305431dbbcade59d2?s=47 Akifumi Fukaya
March 27, 2019
630

MVVM overview

Ee4f2266abb0268305431dbbcade59d2?s=128

Akifumi Fukaya

March 27, 2019
Tweet

Transcript

  1. MVVM overview @akifumi

  2. 自己紹介 • 名前 ◦ 深谷 哲史(ふかや あきふみ) • 会社 ◦

    株式会社メルペイ(2018/06 ~ ) • 職種 ◦ ソフトウェアエンジニア( iOS) • アカウント ◦ Twitter: @akifumifukaya ◦ Facebook: Akifumi Fukaya ◦ Github: akifumi 2
  3. About MVVM • ソフトウェアアーキテクチャパターンのひとつ • Model, View, ViewModel に分ける ◦

    Model ▪ データの管理 ▪ ドメインモデル ▪ ビジネスロジック ◦ View ▪ ユーザーインターフェイス ◦ ViewModel ▪ View と Model をつなぐ • merpay-ios-sdk では、MVVMを採用している ◦ ※ merpay-ios-sdk は、メルカリ内のメルペイの機能を提供しているもの
  4. MVVM in iOS ViewController ViewModel Model data inputs outputs

  5. ListViewModelを考えてみる

  6. MVVM in iOS ListViewController ListViewModel Item data inputs outputs

  7. Interface of ViewModel • ViewModelInputs ◦ View → ViewModel への入力を定義

    • ViewModelOutputs ◦ ViewModel → View への出力を定義 • ViewModelType ◦ ViewModelのインターフェイス定義
  8. MVVM in iOS ListViewController ListViewModelInputs ListViewModelOutputs ListViewModel ListViewModelType

  9. ListViewModelInputs protocol ListViewModelInputs { func viewDidLoad() func tableViewDidSelectRow(section: Int, row:

    Int) }
  10. ListViewModelOutputs protocol ListViewModelOutputs: class { typealias Section = ListViewModel.Section typealias

    Alert = ListViewModel.Alert var sections: [Section] { get } var reloadData: (() -> Void)? { get set } var showAlert: ((Alert) -> Void)? { get set } }
  11. ListViewModelType protocol ListViewModelType { var inputs: ListViewModelInputs { get }

    var outputs: ListViewModelOutputs { get } }
  12. ListViewModelType protocol ListViewModelInputs { func viewDidLoad() func tableViewDidSelectRow(section: Int, row:

    Int) } protocol ListViewModelOutputs: class { typealias Section = ListViewModel.Section typealias Alert = ListViewModel.Alert var sections: [Section] { get } var reloadData: (() -> Void)? { get set } var showAlert: ((Alert) -> Void)? { get set } } protocol ListViewModelType { var inputs: ListViewModelInputs { get } var outputs: ListViewModelOutputs { get } }
  13. ListViewModel final class ListViewModel: ListViewModelType, ListViewModelInputs, ListViewModelOutputs {}

  14. ListViewModel final class ListViewModel: ListViewModelType, ListViewModelInputs, ListViewModelOutputs { enum Section

    { case items([Item]) } struct Alert { let title: String let message: String let completion: (() -> Void)? } }
  15. ListViewModel final class ListViewModel: ListViewModelType, ListViewModelInputs, ListViewModelOutputs { … //

    MARK: - ListViewModelType var inputs: ListViewModelInputs { return self } var outputs: ListViewModelOutputs { return self } }
  16. ListViewModel final class ListViewModel: ListViewModelType, ListViewModelInputs, ListViewModelOutputs { … //

    MARK: - ListViewModelInputs func viewDidLoad() { reload() } func tableViewDidSelectRow(section: Int, row: Int) { // do-something } }
  17. ListViewModel final class ListViewModel: ListViewModelType, ListViewModelInputs, ListViewModelOutputs { … //

    MARK: - ListViewModelOutputs private(set) var sections: [Section] = [] { didSet { reloadData?() } } var reloadData: (() -> Void)? var showAlert: ((Alert) -> Void)? }
  18. ListViewController final class ListViewController: UIViewController { private var viewModel: ListViewModelType

    init(viewModel: ListViewModelType = ListViewModel()) { self.viewModel = viewModel super.init(nibName: nil, bundle: Bundle.main) } @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
  19. ListViewController final class ListViewController: UIViewController { typealias Alert = ListViewModelOutputs.Alert

    @IBOutlet private var tableView: UITableView! … override func viewDidLoad() { super.viewDidLoad() bind() viewModel.inputs.viewDidLoad() } private func bind() { viewModel.outputs.reloadData = { [weak self] in self?.tableView.reloadData() } viewModel.outputs.showAlert = { [weak self] alert in self?.showAlert(with: alert) } } private func showAlert(with alert: Alert) { UIAlertController.ok(title: alert.title, message: alert.message, in: self, completion: { _ in alert.completion?() }) } }
  20. Unit tests • merpay-ios-sdk では、ほぼ全てのViewControllerの実装でMVVMアーキテクチャ を採用している • ViewModelに対して、ユニットテストを書くようにして品質を向上している

  21. ListViewModelTests import XCTest @testable import ViewModelSample class ListViewModelTests: XCTestCase {

    private var viewModel: ListViewModelType! override func setUp() { super.setUp() viewModel = ListViewModel() } override func tearDown() { viewModel = nil super.tearDown() } }
  22. ListViewModelTests class ListViewModelTests: XCTestCase { private var viewModel: ListViewModelType! …

    func testSections() { viewModel.inputs.viewDidLoad() XCTAssertEqual(viewModel.outputs.sections.count, 1) guard case let .items(rows) = viewModel.outputs.sections[0] else { return XCTFail() } XCTAssertEqual(rows.count, 10) XCTAssertEqual(rows[0].name, "Item 0") } }
  23. MVVM Advantages • 可読性: View, Model, ViewModel に分割する ◦ 役割を明確に分けることができる

    ◦ クラス単位のコード量が減る ◦ コードの見通してが良くなる • テスタビリティ: コードの品質が向上する ◦ ViewModelにロジックを分割し、ロジックに対してテストを書くこと容易になる ◦ • 拡張性 ◦ ViewModelを分割し、ViewModelType/ViewModelInputs/ViewModelOutputsとインターフェイスを 設けていることにより、 ViewModelの入れ替えが容易
  24. Code coverage of merpay-ios-sdk

  25. Thank you for your attention.