如何在MVVM中更新变量?

时间:2018-11-30 07:55:33

标签: swift mvvm

我正在尝试使用MVVM。我将从VC2VC1。我正在更新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()中呼叫viewDidLoad,并且fromVC打印为0而不是8。有帮助吗?

1 个答案:

答案 0 :(得分:0)

对于MVVM模式,您需要了解它是一个分为两个不同部分的层:输入和输出。

在输入方面,您的viewModel需要捕获viewController中的每个事件,对于Outputs,这是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"
    }
}

由于viewController正在处理touchUp事件并拥有要带到标签的数据,因此我们需要提取对viewModel的职责。

通过提取此内容,您将正确确定职责,毕竟,您将能够正确测试viewModel

那怎么办呢?简单,让我们看一下我们的未来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)
    }
}

就是这样