您好,欢迎来到二三娱乐。
搜索
您的当前位置:首页RxSwift MVVM最佳实践:Inputs - Output

RxSwift MVVM最佳实践:Inputs - Output

来源:二三娱乐

当谈到iOS架构时,MVVM是个很好的选择。它不仅提供了比MVC更好的可测试性,而且与VIPER等同类架构相比,这个架构是轻量级的。尽管如此,应该采用适当的方法来利用MVVM。否则,我们最终可能会使用一个类似的MVC版本,并添加一个额外的组件(ViewModel)。

这篇文章介绍了一种叫Inputs - Outputs的方法,目前在Kickstarter上使用。在Kickstarter iOS应用中你可以看到大多以这种风格的编程方式。

Principles 原则

  • Inputs是一组对ViewModel有影响的操作和事件,比如按钮上的tap操作,或者viewDidLoad事件。
  • Outputs表示视图需要去响应的变化。(译者注:比如数据的改变)
  • 由于ouput可能会随时间变化,所以最好在每个ouput中返回一个可观察的(在RxSwift上下文中)。
  • 在output中定义的行为不应该被表示为变量,因为我们不需要可观察的输入。

How-to? 怎么做

protocol LoginViewModelInputsType {
    func viewDidLoad()
    func tapSubmit()
    func type(email: String)
    func type(password: String)
}

protocol LoginViewModelOutputsType {
    var validInput: Observable<Bool> { get }
    var isLoading: Observable<Bool> { get }
    var loginSuccess: Observable<Void> { get }
    var loginFailure: Observable<ErrorMessage> { get }
}

protocol LoginViewModelType {
    var inputs: LoginViewModelInputsType { get }
    var ouputs: LoginViewModelOutputsType { get }
}

这就是LoginViewModel的样子:

final class LoginViewModel: LoginViewModelType, LoginViewModelInputsType, LoginViewModelOutputsType {
    var inputs: LoginViewModelInputsType { return self }
    var ouputs: LoginViewModelOutputsType { return self }

    // MARK: - Inputs
    private let _tapSubmit = Variable<Void>(())
    func tapSubmit() { 
        _tapSubmit.value = ()
    }

    private let _email = Variable<String>("")
    func type(email: String) {
        _email.value = email
    }
    ...

    // MARK: - Ouputs
    private let _loginSuccess = Variable<Void>(())
    var loginSuccess: Observable<Void> { return _loginSuccess.skip(1) }
    ...

    init() {
        let loginObservable = _tapSubmit.asObservable().skip(1)
            .flatMapLatest(login)
            .share()

        loginObservable
            .bind(to: _loginSuccess)
            .diposed(by: disposeBag)

        // Binding for `_loginFailure` and `isLoading` goes here
        ...
    }
}

Why Inputs - Outputs? 为什么使用Inputs - Outputs

首先,通过使用这样的协议,我们实现了更高层次的抽象。因此,我们的代码更加面向行为,更容易测试。

这种基于协议的约定的另一个优点是单元测试的可读性。让我们来看看下面这两个简单的测试:

func test_When_PasswordIsEmpty_Then_InputIsInvalid() {
    viewModel.inputs.viewDidLoad()
    viewModel.inputs.type(email: 

    // `grabLatestValue` is just an utility function we can write to retrieve
    // the latest value in the stream (during a specific period of time).
    // `RxBlocking` comes for the rescue.
    let validInput = grabLatestValue(viewModel.outputs.validInput, duration: 1)
    expect(validInput).to(beFalse())
}

func test_When_Submitting_Then_ShouldShowLoadingAndThenHideWhenCompleted() {
    viewModel.inputs.viewDidLoad()
    viewModel.inputs.type(email: 
    viewModel.inputs.type(password: "Password0")
    viewModel.inputs.tapSubmit()

    let loadingStates = grabLatestValue(viewModel.outputs.isLoading, duration: 1)
    expect(loadingStates).to(beEqual([true, false]))
}

通过查看与输入调用相关的代码,我们很快就能了解我们试图模拟的场景。同样,我们期望看到的是反映在输出上。

引用

Copyright © 2019- yule263.com 版权所有 湘ICP备2023023988号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务