web-dev-qa-db-ja.com

iOSでのMVVMの使用

私はiOS開発者であり、プロジェクトにマッシブビューコントローラーがあることに罪があるので、プロジェクトを構造化するためのより良い方法を探していて、MVVM(Model-View-ViewModel)アーキテクチャに出くわしました。私はiOSで多くのMVVMを読んでいますが、いくつか質問があります。問題を例を挙げて説明します。

LoginViewControllerというビューコントローラがあります。

LoginViewController.Swift

_import UIKit

class LoginViewController: UIViewController {

    @IBOutlet private var usernameTextField: UITextField!
    @IBOutlet private var passwordTextField: UITextField!

    private let loginViewModel = LoginViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    @IBAction func loginButtonPressed(sender: UIButton) {
        loginViewModel.login()
    }
}
_

Modelクラスはありません。しかし、検証ロジックとネットワーク呼び出しを配置するためにLoginViewModelというビューモデルを作成しました。

LoginViewModel.Swift

_import Foundation

class LoginViewModel {

    var username: String?
    var password: String?

    init(username: String? = nil, password: String? = nil) {
        self.username = username
        self.password = password
    }

    func validate() {
        if username == nil || password == nil {
            // Show the user an alert with the error
        }
    }

    func login() {
        // Call the login() method in ApiHandler
        let api = ApiHandler()
        api.login(username!, password: password!, success: { (data) -> Void in
            // Go to the next view controller
        }) { (error) -> Void in
            // Show the user an alert with the error
        }
    }
}
_
  1. 私の最初の質問は、単にMVVMの実装が正しいかどうかです。たとえば、ログインボタンのタップイベント(loginButtonPressed)をコントローラーに配置したため、この疑問があります。いくつかのテキストフィールドとボタンしかないため、ログイン画面用に個別のビューを作成しませんでした。コントローラーがUI要素に関連付けられたイベントメソッドを持つことは許容されますか?

  2. 次の質問は、ログインボタンについてもです。ユーザーがボタンをタップすると、検証のためにユーザー名とパスワードの値がLoginViewModelに渡され、成功した場合はAPI呼び出しに渡されます。値をビューモデルに渡す方法について質問します。 2つのパラメーターをlogin()メソッドに追加して、ビューコントローラーから呼び出すときにそれらを渡す必要がありますか?または、ビューモデルでそれらのプロパティを宣言し、それらの値をビューコントローラーから設定する必要がありますか? MVVMで許容できるのはどれですか?

  3. ビューモデルでvalidate()メソッドを取得します。どちらかが空の場合は、ユーザーに通知する必要があります。つまり、チェック後、結果をビューコントローラーに返して必要なアクションを実行する必要があります(アラートを表示)。 login()メソッドでも同じことが言えます。リクエストが失敗した場合はユーザーに警告し、成功した場合は次のView Controllerに進みます。ビューモデルからこれらのイベントをコントローラーに通知するにはどうすればよいですか?このような場合にKVOのようなバインディングメカニズムを使用することは可能ですか?

  4. IOSでMVVMを使用する場合、他のバインディングメカニズムは何ですか? KVOは1つです。しかし、多くのボイラープレートコード(オブザーバーの登録/登録解除など)を必要とするため、大規模なプロジェクトにはあまり適していないと私は読んだ他のオプションは何ですか? ReactiveCocoaがこれに使用されるフレームワークであることは知っていますが、他にネイティブなものがあるかどうかを確認しようとしています。

私がインターネット上のMVVMで見つけたすべての資料は、私が明確にしたいこれらの部分に関する情報をほとんどまたはまったく提供していませんでしたので、私は本当にあなたの応答に感謝します。

5
Isuru

私はこれらの質問に自分で取り組んでいるだけですが、私はできる限り最良の答えを与え、いくつかの観察を行います。

IOSのMVCは、プログラムをモデル、ビュー、および特定のビューを特定のモデルにリンクするコントローラーに分割することを期待していると言われています。悲しいことに、実際にはいくつかのビューといくつかのモデルがありますが、すべてのリンケージを処理する(View)Controllerは1つだけです。これが大規模なViewControllerにつながるものです。

このコンテキストでMVVMを採用するとは、ViewControllerを2つの部分に分割することを意味します。ビューを直接処理する部分(適切なViewControllerクラスにとどまります)と、モデルを直接処理する部分(ModelViewクラス/構造体に移動されます。)

これは、only ViewControllerがビューとの間でメッセージを送受信している必要があり、ビュー以外のオブジェクトであるVCと対話する必要があるのはModelViewのみであることを意味します。 ModelViewは、さまざまなすべてのModelオブジェクトと対話する必要があり、ビューと対話することはできません。

上記のViewController/ViewModel分割の概念は、質問に対する次の回答に当てはまります。

  1. いいえ、MVVMの実装は正しくありません。 ViewModelメソッドには根本的な欠陥があります。ユーザーがアラートを表示する責任を負わないように、ビューオブジェクトと直接対話するべきではありません。それがViewControllerの仕事です。 ViewModelは、ViewControllers間の遷移も担当していません。

  2. ViewControllerはモデルオブジェクトを処理するべきではありません。ビューとModelViewとのみ会話する必要があります。したがって、ViewControllerはnotでビューからユーザー名とパスワードを引き出し、ViewModelのログイン関数に渡す必要があります。そのためには、ViewControllerがユーザー名とパスワードの違いについて、またそれらを使用するのが適切な場合について理解する必要があります。

代わりに、ViewControllerは、ユーザー名とパスワードのフィールドで変更が発生したときにViewModelに通知する必要があります。ログインボタンが押されたときにViewModelに通知する必要があります。特定のメソッドでviewModelが必要とするモデルオブジェクトについての知識はありません。

  1. 私はvalidate()関数がBoolを返すことを期待します(isValidを呼び出してそれを計算されたプロパティにすることをお勧めします。)login()関数が2つを受け入れることを期待しますブロック(1つは合格、もう1つは不合格)。正しいブロックを呼び出すことで、何が起こったかを通知できます。もう1つのオプションは、関数がsuccessブール値を持つパラメーターとして1つのブロックを受け入れるようにすることです。だからこのようなもの:

-

_struct LoginViewModel {

    var username: String = ""
    var password: String = ""

    init(username: String, password: String) {
        self.username = username
        self.password = password
    }

    var isValid: Bool {
        return !username.isEmpty && !password.isEmpty
    }

    func login(callback: (error: NSError?) -> Void) {
        let api = ApiHandler()
        api.login(username, password: password, success: { (data) -> Void in
            callback(error: nil)
            }) { (error) -> Void in
                callback(error: error)
        }
    }

}
_
  1. MVVMパラダイムでは、ReactiveCocoa(およびRxSwiftは別のライブラリ)を使用して、ModelViewの変更をViewControllerに通知しますが、これについてお聞きします... ModelViewは、ViewControllerが変更しない実質的な方法で変更されますか?について知る?これまでのところ、アタッチされているViewController以外のオブジェクトがModelViewにメッセージを送信している状況はありません。そのため、ViewControllerが少なくとも可能であることを認識せずにModelViewを変更する方法はありません。このため、通常、ViewControllerにupdateUI()関数があります。 ViewControllerは、潜在的に変化するメッセージをModelViewに送信するたびに、updateUI()を呼び出して、ViewModelを調べ、それに応じてUIを更新します。トリックは必要ありません。

-これがあなたのお役に立てば幸いです。うまくいけば、私の答えがこの質問に関するいくつかの議論を刺激するでしょう。

6
Daniel T.