Slide 1

Slide 1 text

MVVM overview @akifumi

Slide 2

Slide 2 text

自己紹介 ● 名前 ○ 深谷 哲史(ふかや あきふみ) ● 会社 ○ 株式会社メルペイ(2018/06 ~ ) ● 職種 ○ ソフトウェアエンジニア( iOS) ● アカウント ○ Twitter: @akifumifukaya ○ Facebook: Akifumi Fukaya ○ Github: akifumi 2

Slide 3

Slide 3 text

About MVVM ● ソフトウェアアーキテクチャパターンのひとつ ● Model, View, ViewModel に分ける ○ Model ■ データの管理 ■ ドメインモデル ■ ビジネスロジック ○ View ■ ユーザーインターフェイス ○ ViewModel ■ View と Model をつなぐ ● merpay-ios-sdk では、MVVMを採用している ○ ※ merpay-ios-sdk は、メルカリ内のメルペイの機能を提供しているもの

Slide 4

Slide 4 text

MVVM in iOS ViewController ViewModel Model data inputs outputs

Slide 5

Slide 5 text

ListViewModelを考えてみる

Slide 6

Slide 6 text

MVVM in iOS ListViewController ListViewModel Item data inputs outputs

Slide 7

Slide 7 text

Interface of ViewModel ● ViewModelInputs ○ View → ViewModel への入力を定義 ● ViewModelOutputs ○ ViewModel → View への出力を定義 ● ViewModelType ○ ViewModelのインターフェイス定義

Slide 8

Slide 8 text

MVVM in iOS ListViewController ListViewModelInputs ListViewModelOutputs ListViewModel ListViewModelType

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

ListViewModelType protocol ListViewModelType { var inputs: ListViewModelInputs { get } var outputs: ListViewModelOutputs { get } }

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

ListViewModel final class ListViewModel: ListViewModelType, ListViewModelInputs, ListViewModelOutputs {}

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

ListViewModel final class ListViewModel: ListViewModelType, ListViewModelInputs, ListViewModelOutputs { … // MARK: - ListViewModelOutputs private(set) var sections: [Section] = [] { didSet { reloadData?() } } var reloadData: (() -> Void)? var showAlert: ((Alert) -> Void)? }

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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?() }) } }

Slide 20

Slide 20 text

Unit tests ● merpay-ios-sdk では、ほぼ全てのViewControllerの実装でMVVMアーキテクチャ を採用している ● ViewModelに対して、ユニットテストを書くようにして品質を向上している

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

MVVM Advantages ● 可読性: View, Model, ViewModel に分割する ○ 役割を明確に分けることができる ○ クラス単位のコード量が減る ○ コードの見通してが良くなる ● テスタビリティ: コードの品質が向上する ○ ViewModelにロジックを分割し、ロジックに対してテストを書くこと容易になる ○ ● 拡張性 ○ ViewModelを分割し、ViewModelType/ViewModelInputs/ViewModelOutputsとインターフェイスを 設けていることにより、 ViewModelの入れ替えが容易

Slide 24

Slide 24 text

Code coverage of merpay-ios-sdk

Slide 25

Slide 25 text

Thank you for your attention.