私は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
}
}
}
_
私の最初の質問は、単にMVVMの実装が正しいかどうかです。たとえば、ログインボタンのタップイベント(loginButtonPressed
)をコントローラーに配置したため、この疑問があります。いくつかのテキストフィールドとボタンしかないため、ログイン画面用に個別のビューを作成しませんでした。コントローラーがUI要素に関連付けられたイベントメソッドを持つことは許容されますか?
次の質問は、ログインボタンについてもです。ユーザーがボタンをタップすると、検証のためにユーザー名とパスワードの値がLoginViewModelに渡され、成功した場合はAPI呼び出しに渡されます。値をビューモデルに渡す方法について質問します。 2つのパラメーターをlogin()
メソッドに追加して、ビューコントローラーから呼び出すときにそれらを渡す必要がありますか?または、ビューモデルでそれらのプロパティを宣言し、それらの値をビューコントローラーから設定する必要がありますか? MVVMで許容できるのはどれですか?
ビューモデルでvalidate()
メソッドを取得します。どちらかが空の場合は、ユーザーに通知する必要があります。つまり、チェック後、結果をビューコントローラーに返して必要なアクションを実行する必要があります(アラートを表示)。 login()
メソッドでも同じことが言えます。リクエストが失敗した場合はユーザーに警告し、成功した場合は次のView Controllerに進みます。ビューモデルからこれらのイベントをコントローラーに通知するにはどうすればよいですか?このような場合にKVOのようなバインディングメカニズムを使用することは可能ですか?
IOSでMVVMを使用する場合、他のバインディングメカニズムは何ですか? KVOは1つです。しかし、多くのボイラープレートコード(オブザーバーの登録/登録解除など)を必要とするため、大規模なプロジェクトにはあまり適していないと私は読んだ他のオプションは何ですか? ReactiveCocoaがこれに使用されるフレームワークであることは知っていますが、他にネイティブなものがあるかどうかを確認しようとしています。
私がインターネット上のMVVMで見つけたすべての資料は、私が明確にしたいこれらの部分に関する情報をほとんどまたはまったく提供していませんでしたので、私は本当にあなたの応答に感謝します。
私はこれらの質問に自分で取り組んでいるだけですが、私はできる限り最良の答えを与え、いくつかの観察を行います。
IOSのMVCは、プログラムをモデル、ビュー、および特定のビューを特定のモデルにリンクするコントローラーに分割することを期待していると言われています。悲しいことに、実際にはいくつかのビューといくつかのモデルがありますが、すべてのリンケージを処理する(View)Controllerは1つだけです。これが大規模なViewControllerにつながるものです。
このコンテキストでMVVMを採用するとは、ViewControllerを2つの部分に分割することを意味します。ビューを直接処理する部分(適切なViewControllerクラスにとどまります)と、モデルを直接処理する部分(ModelViewクラス/構造体に移動されます。)
これは、only ViewControllerがビューとの間でメッセージを送受信している必要があり、ビュー以外のオブジェクトであるVCと対話する必要があるのはModelViewのみであることを意味します。 ModelViewは、さまざまなすべてのModelオブジェクトと対話する必要があり、ビューと対話することはできません。
上記のViewController/ViewModel分割の概念は、質問に対する次の回答に当てはまります。
いいえ、MVVMの実装は正しくありません。 ViewModelメソッドには根本的な欠陥があります。ユーザーがアラートを表示する責任を負わないように、ビューオブジェクトと直接対話するべきではありません。それがViewControllerの仕事です。 ViewModelは、ViewControllers間の遷移も担当していません。
ViewControllerはモデルオブジェクトを処理するべきではありません。ビューとModelViewとのみ会話する必要があります。したがって、ViewControllerはnotでビューからユーザー名とパスワードを引き出し、ViewModelのログイン関数に渡す必要があります。そのためには、ViewControllerがユーザー名とパスワードの違いについて、またそれらを使用するのが適切な場合について理解する必要があります。
代わりに、ViewControllerは、ユーザー名とパスワードのフィールドで変更が発生したときにViewModelに通知する必要があります。ログインボタンが押されたときにViewModelに通知する必要があります。特定のメソッドでviewModelが必要とするモデルオブジェクトについての知識はありません。
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)
}
}
}
_
updateUI()
関数があります。 ViewControllerは、潜在的に変化するメッセージをModelViewに送信するたびに、updateUI()
を呼び出して、ViewModelを調べ、それに応じてUIを更新します。トリックは必要ありません。-これがあなたのお役に立てば幸いです。うまくいけば、私の答えがこの質問に関するいくつかの議論を刺激するでしょう。