swift - 如何更新 MVVM 中的变量?
问题描述
我正在尝试使用 MVVM。我VC2
要从VC1
. 我正在更新viewModel.fromVC = 1
,但值没有在VC2
.
这就是我的意思:
有一个viewModel
,里面有一个var fromVC = Int()
。现在,在 中vc1
,我称其viewModel
为
let viewModel = viewModel()
.
现在,点击按钮,我正在更新viewModel.fromVC = 8
. 并且,移动到下一个屏幕。在下一个屏幕中,当我打印时,fromVC
我得到的值为 0 而不是 8。
这就是VC2
外观
class VC2 {
let viewModel = viewModel()
func abc() {
print(viewModel.fromVC)
}
}
现在,我正在打电话abc()
,打印为 0 而不是 8。有什么帮助吗viewDidLoad
?fromVC
解决方案
对于该MVVM
模式,您需要了解它是一个分为 2 个不同部分的层:输入和输出。
就输入而言,您的 viewModel 需要从 viewController 捕获每个事件,而对于输出,这是 viewModel 将数据(正确格式化)发送到 viewController 的方式。
所以基本上,如果我们有一个这样的 viewController:
final class HomeViewController: UIViewController {
// MARK: - Outlets
@IBOutlet private weak var titleLabel: UILabel!
// MARK: - View life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Actions
@IBAction func buttonTouchUp(_ sender: Any) {
titleLabel.text = "toto"
}
}
我们需要将职责提取到 viewModel,因为 viewController 正在处理 touchUp 事件,并拥有要带到标签的数据。
通过提取它,您将正确确定责任,毕竟,您将能够正确测试您的 viewModel
那么该怎么做呢?简单,让我们看看我们的未来视图模型:
final class HomeViewModel {
// MARK: - Private properties
private let title: String
// MARK: - Initializer
init(title: String) {
self.title = title
}
// MARK: - Outputs
var titleText: ((String) -> Void)?
// MARK: - Inputs
func viewDidLoad() {
titleText?("")
}
func buttonDidPress() {
titleText?(title)
}
}
所以现在,通过这样做,您可以保证不同职责的安全,让我们看看如何将我们的 viewModel 绑定到我们之前的 viewController :
final class HomeViewController: UIViewController {
// MARK: - public var
var viewModel: HomeViewModel!
// MARK: - Outlets
@IBOutlet private weak var titleLabel: UILabel!
// MARK: - View life cycle
override func viewDidLoad() {
super.viewDidLoad()
bind(to: viewModel)
viewModel.viewDidLoad()
}
// MARK: - Private func
private func bind(to viewModel: HomeViewModel) {
viewModel.titleText = { [weak self] title in
self?.titleLabel.text = title
}
}
// MARK: - Actions
@IBAction func buttonTouchUp(_ sender: Any) {
viewModel.buttonDidPress()
}
}
所以缺少一件事,你会问我“但是如何在 viewController 中初始化我们的 viewModel?”
基本上你应该再次提取职责,你可以有一个Screens
层来负责创建这样的视图:
final class Screens {
// MARK: - Properties
private let storyboard = UIStoryboard(name: StoryboardName, bundle: Bundle(for: Screens.self))
// MARK: - Home View Controller
func createHomeViewController(with title: String) -> HomeViewController {
let viewModel = HomeViewModel(title: title)
let viewController = storyboard.instantiateViewController(withIdentifier: "Home") as! HomeViewController
viewController.viewModel = viewModel
return viewController
}
}
最后做这样的事情:
let screens = Screens()
let homeViewController = screens.createHomeViewController(with: "Toto")
但主要的主题是带来正确测试它的可能性,那么怎么做呢?很容易!
import XCTest
@testable import mvvmApp
final class HomeViewModelTests: XCTestCase {
func testGivenAHomeViewModel_WhenViewDidLoad_titleLabelTextIsEmpty() {
let viewModel = HomeViewModel(title: "toto")
let expectation = self.expectation("Returned title")
viewModel.titleText = { title in
XCTAssertEqual(title, "")
expectation.fulfill()
}
viewModel.viewDidLoad()
waitForExpectations(timeout: 1.0, handler: nil)
}
func testGivenAHomeViewModel_WhenButtonDidPress_titleLabelTextIsCorrectlyReturned() {
let viewModel = HomeViewModel(title: "toto")
let expectation = self.expectation("Returned title")
var counter = 0
viewModel.titleText = { title in
if counter == 1 {
XCTAssertEqual(title, "toto")
expectation.fulfill()
}
counter += 1
}
viewModel.viewDidLoad()
viewModel.buttonDidPress()
waitForExpectations(timeout: 1.0, handler: nil)
}
}
就是这样
推荐阅读
- zigbee - ZigBee 网络配置
- c# - 从两个字符串c#中获取所有唯一组合
- ios - 使用 macOS Catalina beta 在 Xcode 11 中自动预览
- java - Spring Boot ThreadPoolTaskExecutor 和 server.tomcat.max-threads 属性的区别
- regex - 正则表达式从 10 个字符串(数字和文本)中取出前 4 或 5 个字符
- promela - 多次重复..直到在 Promela Spin 中
- neo4j - 如何在neo4j中模拟远足路径?
- php - 将本地主机重定向到项目文件夹
- laravel - 无法在 Heroku 上获取 Laravel API 数据
- javascript - 使用python获取空白电子邮件