gpt4 book ai didi

ios - MVVM 在 iOS 中的使用

转载 作者:IT王子 更新时间:2023-10-29 07:46:00 24 4
gpt4 key购买 nike

我是一名 iOS 开发人员,我对在我的项目中使用 Massive View Controller 感到内疚,所以我一直在寻找一种更好的方式来构建我的项目,并遇到了 MVVM (Model-View-ViewModel) 架构。我已经阅读了很多关于 iOS 的 MVVM,我有几个问题。我将用一个例子来解释我的问题。

我有一个名为 LoginViewController 的 View Controller 。

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

它没有模型类。但我确实创建了一个名为 LoginViewModel 的 View 模型来放置验证逻辑和网络调用。

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) 放在 Controller 中。我没有为登录屏幕创建单独的 View ,因为它只有几个文本字段和一个按钮。 Controller 可以将事件方法绑定(bind)到 UI 元素吗?

  2. 我的下一个问题也是关于登录按钮的。当用户点击按钮时,用户名和密码值应该传递给 LoginViewModel 进行验证,如果成功,则传递给 API 调用。我的问题是如何将值传递给 View 模型。我是否应该向 login() 方法添加两个参数并在我从 View Controller 调用它时传递它们?或者我应该在 View 模型中为它​​们声明属性并从 View Controller 设置它们的值?哪个在 MVVM 中是可以接受的?

  3. 在 View 模型中使用validate() 方法。如果其中任何一个为空,则应通知用户。这意味着在检查之后,结果应该返回给 View Controller 以采取必要的行动(显示警报)。 login() 方法也一样。如果请求失败则提醒用户,如果成功则转到下一个 View Controller 。我如何从 View 模型通知 Controller 这些事件?在这种情况下是否可以使用像 KVO 这样的绑定(bind)机制?

  4. 在 iOS 上使用 MVVM 时还有哪些其他绑定(bind)机制? KVO 就是其中之一。但我读到它不太适合大型项目,因为它需要大量样板代码(注册/注销观察者等)。还有哪些选择?我知道 ReactiveCocoa 是一个用于此的框架,但我正在寻找是否有任何其他本地框架。

我在 Internet 上找到的关于 MVVM 的所有资料几乎没有提供关于我希望澄清的这些部分的信息,因此非常感谢您的回复。

最佳答案

伙计!

1a- 您正朝着正确的方向前进。您将 loginButtonPressed 放在 View Controller 中,这正是它应该在的位置。控件的事件处理程序应始终进入 View Controller - 这是正确的。

1b - 在您的 View 模型中,您有评论指出“向用户显示错误警报”。您不想在验证函数中显示该错误。而是创建一个具有关联值的枚举(其中值是您要向用户显示的错误消息)。更改您的验证方法,以便它返回该枚举。然后在您的 View Controller 中,您可以评估该返回值,并从那里显示警报对话框。请记住,您只想在 View Controller 中使用 UIKit 相关类——永远不要在 View 模型中使用。 View 模型应该只包含业务逻辑。

enum StatusCodes : Equatable
{
case PassedValidation
case FailedValidation(String)

func getFailedMessage() -> String
{
switch self
{
case StatusCodes.FailedValidation(let msg):
return msg

case StatusCodes.OperationFailed(let msg):
return msg

default:
return ""
}
}
}

func ==(lhs : StatusCodes, rhs : StatusCodes) -> Bool
{
switch (lhs, rhs)
{
case (.PassedValidation, .PassedValidation):
return true

case (.FailedValidation, .FailedValidation):
return true

default:
return false
}
}

func !=(lhs : StatusCodes, rhs : StatusCodes) -> Bool
{
return !(lhs == rhs)
}

func validate(username : String, password : String) -> StatusCodes
{
if username.isEmpty || password.isEmpty
{
return StatusCodes.FailedValidation("Username and password are required")
}

return StatusCodes.PassedValidation
}

2 - 这是一个偏好问题,最终取决于您的应用程序的要求。在我的应用程序中,我通过 login() 方法传递这些值,即 login(username, password)。

3 - 创建一个名为 LoginEventsDelegate 的协议(protocol),然后在其中包含一个方法:

func loginViewModel_LoginCallFinished(successful : Bool, errMsg : String)

然而,这个方法应该只用于通知 View Controller 尝试登录远程服务器的实际结果。它应该与验证部分无关。您的验证例程将按照上面#1 中的讨论进行处理。让您的 View Controller 实现 LoginEventsDelegate。并在您的 View 模型上创建一个公共(public)属性,即

class LoginViewModel {
var delegate : LoginEventsDelegate?
}

然后在您的 api 调用的完成 block 中,您可以通过委托(delegate)通知 View Controller ,即

func login() {
// Call the login() method in ApiHandler
let api = ApiHandler()

let successBlock =
{
[weak self](data) -> Void in

if let this = self {
this.delegate?.loginViewModel_LoginCallFinished(true, "")
}
}

let errorBlock =
{
[weak self] (error) -> Void in

if let this = self {
var errMsg = (error != nil) ? error.description : ""
this.delegate?.loginViewModel_LoginCallFinished(error == nil, errMsg)
}
}

api.login(username!, password: password!, success: successBlock, error: errorBlock)
}

你的 View Controller 看起来像这样:

class loginViewController : LoginEventsDelegate {

func viewDidLoad() {
viewModel.delegate = self
}

func loginViewModel_LoginCallFinished(successful : Bool, errMsg : String) {
if successful {
//segue to another view controller here
} else {
MsgBox(errMsg)
}
}
}

有些人会说您可以将闭包传递给登录方法并完全跳过协议(protocol)。我认为这是个坏主意有几个原因。

将闭包从 UI 层 (UIL) 传递到业务逻辑层 (BLL) 会破坏关注点分离 (SOC)。 Login() 方法驻留在 BLL 中,因此基本上您会说“嘿 BLL 为我执行此 UIL 逻辑”。那是一个 SOC 不不!

BLL 应仅通过委托(delegate)通知与 UIL 通信。这样 BLL 本质上是在说,“嘿 UIL,我已经完成了我的逻辑执行,这里有一些数据参数,您可以根据需要使用它们来操作 UI 控件”。

所以UIL永远不应该要求BLL为他执行UI控制逻辑。应该只要求 BLL 通知他。

4 - 我见过 ReactiveCocoa 并听说过它的优点,但从未使用过它。所以不能从个人经验谈起。我将了解如何在您的场景中使用简单的委托(delegate)通知(如#3 中所述)。如果它满足需求,那就太好了,如果您正在寻找更复杂的东西,那么也许可以看看 ReactiveCocoa。

顺便说一句,这在技术上也不是 MVVM 方法,因为没有使用绑定(bind)和命令,但这只是“ta-may-toe” | “ta-mah-toe”吹毛求疵恕我直言。无论您使用哪种 MV* 方法,SOC 原理都是相同的。

关于ios - MVVM 在 iOS 中的使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27182528/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com