- r - 以节省内存的方式增长 data.frame
- ruby-on-rails - ruby/ruby on rails 内存泄漏检测
- android - 无法解析导入android.support.v7.app
- UNIX 域套接字与共享内存(映射文件)
我是一名 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
}
}
}
我的第一个问题很简单,我的 MVVM 实现是否正确?我有这个疑问,因为例如我将登录按钮的点击事件 (loginButtonPressed
) 放在 Controller 中。我没有为登录屏幕创建单独的 View ,因为它只有几个文本字段和一个按钮。 Controller 可以将事件方法绑定(bind)到 UI 元素吗?
我的下一个问题也是关于登录按钮的。当用户点击按钮时,用户名和密码值应该传递给 LoginViewModel 进行验证,如果成功,则传递给 API 调用。我的问题是如何将值传递给 View 模型。我是否应该向 login()
方法添加两个参数并在我从 View Controller 调用它时传递它们?或者我应该在 View 模型中为它们声明属性并从 View Controller 设置它们的值?哪个在 MVVM 中是可以接受的?
在 View 模型中使用validate()
方法。如果其中任何一个为空,则应通知用户。这意味着在检查之后,结果应该返回给 View Controller 以采取必要的行动(显示警报)。 login()
方法也一样。如果请求失败则提醒用户,如果成功则转到下一个 View Controller 。我如何从 View 模型通知 Controller 这些事件?在这种情况下是否可以使用像 KVO 这样的绑定(bind)机制?
在 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/
IO 设备如何知道属于它的内存中的值在memory mapped IO 中发生了变化? ? 例如,假设内存地址 0 专用于保存 VGA 设备的背景颜色。当我们更改 memory[0] 中的值时,VGA
我目前正在开发一个使用Facebook sdk登录(通过FBLoginView)的iOS应用。 一切正常,除了那些拥有较旧版本的facebook的人。 当他们按下“使用Facebook登录”按钮时,他
假设我有: this - is an - example - with some - dashesNSRange将使用`rangeOfString:@“-”拾取“-”的第一个实例,但是如果我只想要最后
Card.io SDK提供以下详细信息: 卡号,有效期,月份,年份,CVV和邮政编码。 如何从此SDK获取国家名称。 - (void)userDidProvideCreditCardInfo:(Car
iOS 应用程序如何从网络服务下载图片并在安装过程中将它们安装到用户的 iOS 设备上?可能吗? 最佳答案 您无法控制应用在用户设备上的安装,因此无法在安装过程中下载其他数据。 只需在安装后首次启动应
我曾经开发过一款企业版 iOS 产品,我们公司曾将其出售给大型企业,供他们的员工使用。 该应用程序通过 AppStore 提供,企业用户获得了公司特定的配置文件(包含应用程序配置文件)以启用他们有权使
我正在尝试将 Card.io SDK 集成到我的 iOS 应用程序中。我想为 CardIO ui 做一个简单的本地化,如更改取消按钮标题或“在此保留信用卡”提示文本。 我在 github 上找到了这个
我正在使用 CardIOView 和 CardIOViewDelegate 类,没有可以设置为 YES 的 BOOL 来扫描 collectCardholderName。我可以看到它在 CardIOP
我有一个集成了通话工具包的 voip 应用程序。每次我从我的 voip 应用程序调用时,都会在 native 电话应用程序中创建一个新的最近通话记录。我在 voip 应用程序中也有自定义联系人(电话应
iOS 应用程序如何知道应用程序打开时屏幕上是否已经有键盘?应用程序运行后,它可以接收键盘显示/隐藏通知。但是,如果应用程序在分屏模式下作为辅助应用程序打开,而主应用程序已经显示键盘,则辅助应用程序不
我在模拟器中收到以下错误: ImageIO: CGImageReadSessionGetCachedImageBlockData *** CGImageReadSessionGetCachedIm
如 Apple 文档所示,可以通过 EAAccessory Framework 与经过认证的配件(由 Apple 认证)进行通信。但是我有点困惑,因为一些帖子告诉我它也可以通过 CoreBluetoo
尽管现在的调试器已经很不错了,但有时找出应用程序中正在发生的事情的最好方法仍然是古老的 NSLog。当您连接到计算机时,这样做很容易; Xcode 会帮助弹出日志查看器面板,然后就可以了。当您不在办公
在我的 iOS 应用程序中,我定义了一些兴趣点。其中一些有一个 Kontakt.io 信标的名称,它绑定(bind)到一个特定的 PoI(我的意思是通常贴在信标标签上的名称)。现在我想在附近发现信标,
我正在为警报提示创建一个 trigger.io 插件。尝试从警报提示返回数据。这是我的代码: // Prompt + (void)show_prompt:(ForgeTask*)task{
您好,我是 Apple iOS 的新手。我阅读并搜索了很多关于推送通知的文章,但我没有发现任何关于 APNS 从 io4 到 ios 6 的新更新的信息。任何人都可以向我提供 APNS 如何在 ios
UITabBar 的高度似乎在 iOS 7 和 8/9/10/11 之间发生了变化。我发布这个问题是为了让其他人轻松找到答案。 那么:在 iPhone 和 iPad 上的 iOS 8/9/10/11
我想我可以针对不同的 iOS 版本使用不同的 Storyboard。 由于 UI 的差异,我将创建下一个 Storyboard: Main_iPhone.storyboard Main_iPad.st
我正在写一些东西,我将使用设备的 iTunes 库中的一部分音轨来覆盖 2 个视频的组合,例如: AVMutableComposition* mixComposition = [[AVMutableC
我创建了一个简单的 iOS 程序,可以顺利编译并在 iPad 模拟器上运行良好。当我告诉 XCode 4 使用我连接的 iPad 设备时,无法编译相同的程序。问题似乎是当我尝试使用附加的 iPad 时
我是一名优秀的程序员,十分优秀!