68
57

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【iOS】MVVMについて

Last updated at Posted at 2018-09-27

#記事を移動しました。
https://zenn.dev/dd_sho/articles/ec6545ad371137
今後は上記の記事で更新します。(2021/10/03)

MVVMについて一からいろいろ調べて自分なりのあるべき姿が
定まってきたので記事としてまとめます。
RxSwiftの要素はできるだけ排除して、純粋にMVVMについての考察をまとめました。
※ただし実装にはRxSwiftの利用を前提としています。

前提

この記事では以下の考え方で話を進めます。

  1. ビジネスロジックは Model で実装する
  2. データバインディングには RxSwift を利用する
  3. 可能な限り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の処理の流れ

下図のようなイメージです。

MVVMイメージ.png

上段の左から右への処理は
ViewはViewModelを参照し、ViewModelはModelを参照することで進んでいき
下段の右から左への処理は
ModelからViewModelに通知し、ViewModelからViewに通知することで進みます
(なんでViewModel→Viewの場合だけを指してDataBindingって言んだろ。)

ViewModelの構成要素

ViewModelの役割やどう実装するか非常に悩みましたが、にわかながら現状以下のように自分の中でまとまってます。

VMの構成要素.png

皆さんはViewModelをどのように実装されているのでしょうか。
是非コメントお願いします!

検索バーに入力した文字列でGitHubのレポジトリを検索して一覧表示するサンプルを作ってみました。
ViewModelはこんな感じに実装しました。

ViewModel.swift

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責務にする」を考えると以下のような関係が良いのではないかと考えます。

M-V-VMの参照関係.png

View : ViewModel = 1 : 1 (間接的に 1 : n はあり得る)
ViewModel : Model = 1 : 1
Model : Model = n : n

まとめ

ここ2、3日で調べたことを基に考えたことをまとめてみました。
まだ書き足りない所はあるけれど、そこは随時更新していきます。
コードも準備できたらリンク貼りますので、良かったら見てやってください。

まだまだ不十分な部分もあると思いますので、ご指摘いただけますと非常に助かります。

以上です。

68
57
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
68
57

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?