MVVM在iOS中的使用 [英] Usage of MVVM in iOS

查看:36
本文介绍了MVVM在iOS中的使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是一名 iOS 开发人员,我对在我的项目中使用大规模视图控制器感到内疚,所以我一直在寻找一种更好的方式来构建我的项目并遇到了 MVVM(模型-视图-视图模型)架构.我已经阅读了很多关于 iOS 的 MVVM,但我有几个问题.我会用一个例子来解释我的问题.

I'm an iOS developer and I'm guilty of having Massive View Controllers in my projects so I've been searching for a better way to structure my projects and came across the MVVM (Model-View-ViewModel) architecture. I've been reading a lot of MVVM with iOS and I have a couple of questions. I'll explain my issues with an example.

我有一个名为 LoginViewController 的视图控制器.

I have a view controller called 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()
    }
}

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

It doesn't have a Model class. But I did create a view model called LoginViewModel to put the validation logic and network calls.

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 元素?

  1. My first question is simply is my MVVM implementation correct? I have this doubt because for example I put the login button's tap event (loginButtonPressed) in the controller. I didn't create a separate view for the login screen because it has only a couple of textfields and a button. Is it acceptable for the controller to have event methods tied to UI elements?

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

My next question is also about the login button. When the user taps the button, the username and password values should gte passed into the LoginViewModel for validation and if successful, then to the API call. My question how to pass the values to the view model. Should I add two parameters to the login() method and pass them when I call it from the view controller? Or should I declare properties for them in the view model and set their values from the view controller? Which one is acceptable in MVVM?

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

Take the validate() method in the view model. The user should be notified if either of them are empty. That means after the checking, the result should be returned to the view controller to take necessary actions (show an alert). Same thing with the login() method. Alert the user if the request fails or go to the next view controller if it succeeds. How do I notify the controller of these events from the view model? Is it possible to use binding mechanisms like KVO in cases like this?

iOS 使用 MVVM 时的其他绑定机制是什么?KVO 就是其中之一.但我读到它不太适合大型项目,因为它需要大量样板代码(注册/取消注册观察者等).还有哪些选择?我知道 ReactiveCocoa 是一个用于此的框架,但我想看看是否有其他原生框架.

What are the other binding mechanisms when using MVVM for iOS? KVO is one. But I read it's not quite suitable for larger projects because it require a lot of boilerplate code (registering/unregistering observers etc). What are other options? I know ReactiveCocoa is a framework used for this but I'm looking to see if there are any other native ones.

我在互联网上的 MVVM 上遇到的所有材料都没有提供关于我希望澄清的这些部分的信息,因此非常感谢您的回复.

All the materials I came across on MVVM on the Internet provided little to no information on these parts I'm looking to clarify, so I'd really appreciate your responses.

推荐答案

waddup dude!

waddup dude!

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

1a- You're headed in the right direction. You put loginButtonPressed in the view controller and that is exactly where it should be. Event handlers for controls should always go into the view controller - so that is correct.

1b - 在您的视图模型中,您有评论说明,向用户显示带有错误的警报".您不想在验证函数中显示该错误.而是创建一个具有关联值的枚举(其中值是您要向用户显示的错误消息).更改您的验证方法,使其返回该枚举.然后在您的视图控制器中,您可以评估该返回值,然后您将显示警报对话框.请记住,您只想在视图控制器中使用 UIKit 相关类 - 永远不要从视图模型中使用.视图模型应该只包含业务逻辑.

1b - in your view model you have comments stating, "show the user an alert with the error". You don't want to display that error from within the validate function. Instead create an enum that has an associated value (where the value is the error message you want to display to the user). Change your validate method so that it returns that enum. Then within your view controller you can evaluate that return value and from there you will display the alert dialog. Remember you only want to use UIKit related classes only within the view controller - never from the view model. View model should only contain business logic.

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).

2 - this is a matter of preference and ultimately determined by the requirements for your app. In my app I pass these values in via the login() method i.e. login(username, password).

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

3 - Create a protocol named LoginEventsDelegate and then have a method within it as such:

func loginViewModel_LoginCallFinished(successful : Bool, errMsg : String)

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

However this method should only be used to notify the view controller of the actual results of attempting to login on the remote server. It should have nothing to do with the validation portion. Your validation routine will be handled as discussed above in #1. Have your view controller implement the LoginEventsDelegate. And create a public property on your view model i.e.

class LoginViewModel {
    var delegate : LoginEventsDelegate?  
}

然后在您的 api 调用的完成块中,您可以通过委托通知视图控制器,即

Then in the completion block for your api call you can notify the view controller via the delegate i.e.

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

你的视图控制器看起来像这样:

and your view controller would look like this:

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

有些人会说你可以只向登录方法传递一个闭包,然后完全跳过协议.我认为这是一个坏主意的原因有几个.

Some would say you can just pass in a closure to the login method and skip the protocol altogether. There are a few reasons why I think that is a bad idea.

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

Passing a closure from the UI Layer (UIL) to the Business Logic Layer (BLL) would break Separation of Concerns (SOC). The Login() method resides in BLL so essentially you would be saying "hey BLL execute this UIL logic for me". That's an SOC no no!

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

BLL should only communicate with the UIL via delegate notifications. That way BLL is essentially saying, "Hey UIL, I'm finished executing my logic and here's some data arguments that you can use to manipulate the UI controls as you need to".

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

So UIL should never ask BLL to execute UI control logic for him. Should only ask BLL to notify him.

4 - 我见过 ReactiveCocoa 并听说过它的好消息,但从未使用过它.所以不能从个人经验来谈论它.我会看到在您的场景中使用简单的委托通知(如#3 中所述)如何为您工作.如果它满足需求,那就太好了,如果您正在寻找更复杂的东西,那么也许可以看看 ReactiveCocoa.

4 - I've seen ReactiveCocoa and heard good things about it but have never used it. So can't speak to it from personal experience. I would see how using simple delegate notification (as described in #3) works for you in your scenario. If it meets the need then great, if you're looking for something a bit more complex then maybe look into ReactiveCocoa.

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

Btw, this also is technically not an MVVM approach since binding and commands are not being used but that's just "ta-may-toe" | "ta-mah-toe" nitpicking IMHO. SOC principles are all the same regardless of which MV* approach you use.

这篇关于MVVM在iOS中的使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆