RxSwift,ViewModels和UI绑定

时间:2018-04-24 10:22:02

标签: swift mvvm rx-swift

我想知道在以下情况下最佳做法是什么:

  • 项目设置为Model-View-ViewModel
  • 我需要显示包含文字字段的屏幕(例如name),其中用户可以编辑特定Model
  • 的详细信息
  • ViewModel包含Observable<Model>; ViewModel负责从API中检索数据
  • 视图包含需要使用Model
  • 中的当前数据预先填充的输入字段
  • 输入字段需要绑定到例如ViewModel中的BehaviorRelay

我正在努力寻找解决这个问题的最佳方法:

  • 我不想在ViewModel中订阅
  • 我希望ViewModel负责检索数据并填充BehaviorRelay或类似的

我基本上希望有类似于BehaviorRelay的东西,但是来自Observable的初始值。订阅(绑定)到BehaviorRelays后,我希望它们从API检索数据。或flatMap ObservableBehaviorRelay

解决这个问题的最佳方法是什么?

我之前实现过这种方式的方法有其缺点:

  1. 跳过ViewModel中的BehaviorRelay,并将状态保留在ViewController中。只需从ViewController订阅ViewModel中的Observable<Model>并将其绑定到UI。然后,当用户想要保存数据时,从UI(withLatestFrom())中检索最新值,并将其作为值传递给ViewModel中的func save(attribute: Value)。这有效,但我想将所有状态(和业务逻辑)保留在ViewModel中。
  2. 在ViewModel中,使用BehaviorRelay为空的初始值,然后在ViewModel的init()中,检索数据并将其绑定到BehaviorRelay。这意味着在ViewModel中进行订阅,我想避免这种订阅。
  3. 在ViewModel中添加func refresh() -> Completable之类的内容,用于设置BehaviorRelaydo(onNext:) s中的值。这很有效,但感觉很讨厌。
  4. 在ViewModel中有let refresh = PublishSubject<()>(),例如

    var model: Observable<Model> {
      refresh
        .startWith(())
        .flatMapLatest { retrieveData() }
    }
    

    再次使用do(onNext:)。与之前的解决方案相同的缺点(感觉很讨厌)。

  5. 在初始化ViewModel之前检索数据,并使用模型实例初始化ViewModel。我不喜欢这个解决方案,因为我希望ViewModel负责检索它自己的数据。
  6. 还有其他办法吗?

1 个答案:

答案 0 :(得分:0)

我的第一点是,我认为您需要更多地思考一下ViewModel在MVVM中的作用。 viewmodel的目的是从模型转换/修改数据,使其非常适合在那个屏幕上显示。因此,如果您创建一个充满可编辑字段的屏幕,则ViewModel应该将数据拆分为这些离散字段。在Rx中,这通常意味着为每个字段公开Observable(或者如果需要,可以驱动驱动程序)。目标是为view中的viewmodel simple 中的每个属性绑定。该视图不应该执行属于VM的任何表示转换/逻辑。

你的VM不应该进行任何订阅是正确的,ViewController(在MVVM中查看)将对它所显示的每个字段进行订阅。您可以让viewmodel在调用API时使用.share(),并使用生成的observable来提供每个字段可观察对象,这样当视图订阅时,它们都不会自己调用API。它们。

我不确定你需要一个BehaviorRelay是指什么。也许你可以澄清那里的需求。如果是因为你想要一种触发刷新的方法,那么你的第四颗子弹就会让人觉得有意义。你可以像这样设置它

让fetchDataTrigger = PublishRelay();

let dataModelObservable = fetchDataTrigger.asObservable()
    .startWith(())
    .flatMap {
        return goCallMyApi()
    }
    .share()

然后您的每个属性都可以转换dataModelObservable的输出以生成该字段的值。如果您想触发刷新,只需调用fetchDataTrigger.accept(())以响应按钮按下或触发刷新的任何内容。