在初始化之外绑定 ViewModel 事件

Posted

技术标签:

【中文标题】在初始化之外绑定 ViewModel 事件【英文标题】:Binding ViewModel events outside of the init 【发布时间】:2020-12-08 05:10:57 【问题描述】:

我在我的项目中使用 RxSwift 和 Swinject。我绑定输入/输出的方式与 RxSwift 给出的示例并不完全相同。在RxExample/GitHubSignup 中,绑定是在init() 中完成的,对吧?但是我发现很难实现,因为我使用 Swinject+SwinjectStoryboard 对视图控制器进行依赖注入。因此,init() 不可用,因为实例化视图模型的是 Swinject 容器。那么,除了使用init()之外,还有没有办法将视图控制器和视图模型绑定在一起呢?

我在想我可以使用 var 而不是 let 作为输出 observables 并创建一个函数 bind(observables: [Observable]) 或者其他可以执行从输入到输出的绑定和转换的东西。但是因为它们将是 vars 而不是 let,这意味着我们似乎被允许在整个代码中更改绑定。与我们只使用 let 并将它们绑定到 init() 不同。而且,通过使用函数而不是初始化程序,我必须将依赖项存储到成员变量中。而如果我使用初始化器,我可以只转换 mapflatMap 内部的依赖关系。

还有一个问题。说,如果我有这个:

class MyViewController: UIViewController 
    @IBOutlet weak var refreshButton: UIButton!
    @IBOutlet weak var tableView: UITableView!

    var viewModel: MyViewModel!
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() 
        super.viewDidLoad()
        viewModel = MyViewModel(refreshTap: refreshButton.rx.tap, dataProvider: ApiAdapter().getData)
    
    
    
    private func setupEvents() 
        viewModel.tableDTOs.bind(to: tableView.rx.items(
            cellIdentifier: reuseId, cellType: TableViewCell.self))  _, dto, cell in
                cell.fill(with: dto)
        .disposed(by: disposeBag)
    


final class MyViewModel 
    let tableDTOs: Observable<[TableDTO]>
    
    init(refreshTap: Observable<Void>, dataProvider: () -> Observable<[TableDTO]>) 
        tableDTOs = Observable.merge(.just(), refreshTap) //Merge with .just to emit at once for initial values
             .flatMapLatest  dataProvider().asDriver() 
    

那么在这种情况下,如果dataProvider返回completeerror,disposable就会被处理掉,对吧?因此场景将无响应,因为 UI 已经未绑定。知道如何解决这个问题吗?

谢谢。

【问题讨论】:

【参考方案1】:

那么,除了使用init()之外,还有其他方法可以将视图控制器和视图模型绑定在一起吗?

是的,有。为视图模型提供一个函数,该函数接受输入并返回输出。

但是因为它们将是 vars 而不是 let,这意味着我们似乎可以在整个代码中更改绑定。

永远不要将 Observable(或 Subject 或 Observer)设为 var 始终使用 let 函数式响应式编程是一种函数式范例,因此没有 vars。

那么在这种情况下,如果 dataProvider 返回 complete 或 error,disposable 将被释放,对吗?因此场景将无响应,因为 UI 已经未绑定。知道如何解决这个问题吗?

是的,不是的。如果 dataProvider 发出完成的事件,则不会释放,因为 flatMapLatest 仅在 all 的输入完成时释放。由于refreshTap 尚未完成,flatMapLatest 将继续接受来自它的事件,并为每个事件调用它的闭包。

如果 dataProvider 发出错误事件,处理,因为错误事件会使链短路。但是,由于您在 dataProvider 上使用了.asDriver(),因此从闭包返回的 Driver 不可能发出错误事件。你很安全。

阻止错误中断链的其他方法是使用.materialize() 或任何.catchError 运算符。例如:

.flatMapLatest  
    dataProvider
        .map  Result<[TableDTO], Error>.success($0) 
        .catchError  Observable.just(Result<[TableDTO], Error>.failure($0) 

【讨论】:

我明白了。所以输入和输出的整个绑定将来自该函数,对吗?这意味着我不必将输出存储为成员变量,对吗?谢谢。 完全正确。如果你继续沿着这条路走下去,你会开始想知道为什么你一开始就把这个函数放在一个类中。 ?

以上是关于在初始化之外绑定 ViewModel 事件的主要内容,如果未能解决你的问题,请参考以下文章

将事件绑定到 ViewModel

Prism程序入口View ViewModel关联数据绑定数据校验cmd

WPF ViewModel与多个View绑定后如何解决的问题

[WPF源码分析]ContentControl依赖项属性的双向绑定,two-way binding view's DependencyProperty and ViewModel's

WPF:MVVM模式下ViewModel关闭View

Kendo UI - 如何使用 Kendo MVVM 将选中的属性(属性)和处理复选框的单击事件绑定到 viewModel