【iOS】MVVMについて
MVVMについて一からいろいろ調べて自分なりのあるべき姿が
定まってきたので記事としてまとめます。
RxSwiftの要素はできるだけ排除して、純粋にMVVMについての考察をまとめました。
※ただし実装にはRxSwiftの利用を前提としています。→今後はCombineを使うのが一般的になるかなと思います。
前提
この記事では以下の考え方で話を進めます。
- ビジネスロジックは Model で実装する
 - データバインディングには RxSwift を利用する
 - 可能な限り1クラス1責務にする
 
1.について
 MVVMに関する記事を見ているとビジネスロジックをViewModelで実装するか、Modelで実装するか
の2パターンに分かれています。ViewModelで状態を管理しているからビジネスロジックもViewModel
で実装した方が分かりやすい、という意見はよく分かるのですがViewModelの元々の役割を考えると
Modelで実装した方がいいのではと思いました。
2.について
 多くのケースでRxSwiftを採用していることに倣い、RxSwiftを採用します。
 RxSwift便利!!
3.について
 再利用性を考えると1クラス1責務となるのが理想的だと考えています。
MVVMの構成要素
Model
- ビジネスロジック(通信処理やDB操作など)
 - データ型の定義
 
View
ViewModelのデータを、データバインディングで自動的に描画する
ViewModel
- ViewとModel間の伝達
 - Viewのための状態保持
 
MVVMの処理の流れ
下図のようなイメージです。

上段の左から右への処理は
ViewはViewModelを参照し、ViewModelはModelを参照することで進んでいき
下段の右から左への処理は
ModelからViewModelに通知し、ViewModelからViewに通知することで進みます
(なんでViewModel→Viewの場合だけを指してDataBindingって言んだろ。)
ViewModelの構成要素
ViewModelの役割やどう実装するか非常に悩みましたが、にわかながら現状以下のように自分の中でまとまってます。

皆さんはViewModelをどのように実装されているのでしょうか。
是非コメントお願いします!
検索バーに入力した文字列でGitHubのレポジトリを検索して一覧表示するサンプルを作ってみました。
ViewModelはこんな感じに実装しました。
import Foundation
import RxSwift
import RxCocoa
final class RepositoryViewModel {
    
    struct Input {
        // 監視対象(トリガー)
        var repositoryName: Observable<String>
    }
    
    struct Output {
        var rx_repositories: Driver<[RepositoryInfo]>
    }
    
    // MARK: - Properties
    
    // Viewから受け取るトリガー
    private var _input: RepositoryViewModel.Input!
    // Viewにデータをバインドする
    private var _output: RepositoryViewModel.Output!
    // GitHub APIからデータを取得するModel
    private let repositoryModel = RepositoryModel()
    
    // 初期化
    init(trigger: RepositoryViewModel.Input) {
        _input = trigger
        _output = RepositoryViewModel
            .Output.init(rx_repositories: createOutput())
    }
    
    func output() -> RepositoryViewModel.Output {
        return _output
    }
}
extension RepositoryViewModel {
    
    // InputからModel経由でOutputを生成する
    private func createOutput() -> Driver<[RepositoryInfo]> {
        
        return repositoryModel.rx_get(from: _input.repositoryName)
    }
}
プロジェクトにおけるM-V-VMの参照関係
前提の3.「可能な限り1クラス1責務にする」を考えると以下のような関係が良いのではないかと考えます。

View : ViewModel = 1 : 1 (間接的に 1 : n はあり得る)
ViewModel : Model = 1 : 1
Model : Model = n : n
まとめ
ここ2、3日で調べたことを基に考えたことをまとめてみました。
まだ書き足りない所はあるけれど、そこは随時更新していきます。
コードも準備できたらリンク貼りますので、良かったら見てやってください。
まだまだ不十分な部分もあると思いますので、ご指摘いただけますと非常に助かります。
以上です。
Discussion