[RxUI] 18 – When Activated

[RxUI] 18 – When Activated

WhenActivated是一种跟踪可释放对象的方式。除此之外,还可用于延迟ViewModel的设置,直到真正需要为止。WhenActivated还可以启动或停止对“热”可观察对象的响应,例如定期对网络终端执行ping操作或实时更新用户当前位置的后台任务。而且,当ViewModel加载时,可以使用WhenActivated触发启动逻辑。看一个示例:

public class ActivatableViewModel : IActivatableViewModel 
{
    public ViewModelActivator Activator { get; }

    public ActivatableViewModel()
    {
        Activator = new ViewModelActivator();
        this.WhenActivated(disposables => 
        {        
            // Use WhenActivated to execute logic
            // when the view model gets activated.
            this.HandleActivation();

            // Or use WhenActivated to execute logic
            // when the view model gets deactivated.
            Disposable
                .Create(() => this.HandleDeactivation())
                .DisposeWith(disposables);

            // Here we create a hot observable and 
            // subscribe to its notifications. The
            // subscription should be disposed when we'd 
            // like to deactivate the ViewModel instance.
            var interval = TimeSpan.FromMinutes(5);
            Observable
                .Timer(interval, interval)
                .Subscribe(x => { /* do smth every 5m */ })
                .DisposeWith(disposables);

            // We also can observe changes of a
            // property belonging to another ViewModel,
            // so we need to unsubscribe from that
            // changes to ensure we won't have the
            // potential for a memory leak.
            this.WhenAnyValue(...)
                .InvokeCommand(...)
                .DisposeWith(disposables);
        });
    }

    private void HandleActivation() { }

    private void HandleDeactivation() { }
}

如何确保ViewModel被激活?

如果ViewModel实现IVewFor接口,则框架将确保从ViewModel到View的链接。记住要使ViewModel作为DependencyProperty,并且在IVewFor<TVeiwModel>的构造函数中添加对WhenActivated的调用。

视图

每当将一个对象订阅到另一个对象公开的事件时,都会引起内存泄漏。对于基于XAML的平台,尤其是可能不会自动回收依赖属性引用的对象/事件

简而言之,当对象A为对象B公开的事件提供处理程序时,该处理程序将附加到该事件上,并且该处理程序的生存周期与对象B的生存周期相关联。释放对象B时,它的事件处理程序也会被清除,因此在正常情况下,没有必要明确清除事件订阅。但是,在XAML中,依赖属性会带来额外的问题。如果在“Value”属性上订阅了更改事件,即使对象不再使用,也会泄漏该事件,因为该事件与静态属性ValueProperty绑定。

ReactiveUI提供了Dispose模式的变体来帮助解决此问题:

public class ActivatableControl : ReactiveUserControl<ActivatableViewModel>
{
    public ActivatableControl()
    {
        this.InitializeComponent();
        this.WhenActivated(disposables =>
        {   
            // If you put the WhenActivated block into your IViewFor
            // implementation constructor, the view model will also
            // get activated if it implements IActivatableViewModel.

            // Dispose bindings to prevent dependency property memory 
            // leaks that may occur on XAML based platforms. 
            this.Bind(ViewModel, vm => vm.UserName, v => v.Username.Text)
                .DisposeWith(disposables);
            this.BindCommand(ViewModel, vm => vm.Login, v => v.Login)
                .DisposeWith(disposables);

            // Dispose WhenAny bindings to ensure we won't have memory
            // leaking here if the ViewModel outlives the View and vice 
            // versa.
            this.WhenAnyValue(v => v.ViewModel.IsBusy)
                .BindTo(this, v => v.ProgressRing.IsActive)
                .DisposeWith(disposables);
        });
    }
}

作为所有平台的经验法则,应该将其用于绑定和任何超出视图绑定时间的视图设置。这对于设置应添加到可视化树中的内容(即使它们不是可释放的)也非常有用。

何时应该释放IDisposable对象?

如果使用WhenAny处理除了this之外的任何东西,则需要将其放入WhenActivated块中,并且添加对DisposeWith的调用。如果只是启动一个窗口,然后在程序关闭时该窗口关闭,并且没有其它要管理的内容,则不需要WhenActivated

如果要编写this.WhenAnyValue(x => x.ViewModel.Prop).BindTo(...)this.Bind*(...)(在这里了解更多信息),请始终对XAML视图使用WhenActivatedDisposeWith。应该在任何超出视图的视图设置时使用它 — 在XAML平台上,当视图与可视化树分离时,可能不希望保留订阅。当添加到可视化树时,对于设置也很有用,尽管通常正确的位置再ViewModel的WhenActivated中。

  1. 不需要
public MyViewModel()
{
    MyReactiveCommand
        .Execute()
        .Subscribe(...);
}

当ReactiveCommand的执行完成时,所有观察者都将自动取消订阅。通常,对于具有有限生命周期(例如,通过超时)的管道的订阅不需要手动取消。取消此类订阅与释放MemoryStream一样有用。— Kent Boogaart @ You, I and ReactiveUI

  1. 执行Dispose
public MyView()
{
    this.WhenAnyValue(x => x.ViewModel)
        .Do(PopulateFromViewModel)
        .Subscribe();
}

这是一个棘手的问题,如果开发基于依赖属性平台(例如WPF或UWP),则必须取消此订阅。这是因为“没有观察依赖属性的非泄漏方法。(引用自Anaïs Betts)”,这正是ReactiveUserControl的ViewModel属性的确切含义。但是,如果碰巧知道ViewModel不会因视图的外观而改变,则可以将ViewModel设为普通属性,从而无需释放。对于其它平台(例如Xamarin.Forms,Xamarin.Android和Xamarin.iOS),不需要释放,因为只是在监视视图本身的属性(ViewModel),因此订阅将附加到该视图上的PropertyChanged。这意味着视图具有对自身的引用,因此不会阻止对其进行垃圾回收。

  1. 执行Dispose
public MyViewModel()
{
    SomeService.SomePipeline
        .Subscribe(...);
}

服务通常比视图拥有更长的生命周期,尤其是在单例和全局应用程序变量的情况下。因此,取消这些类型的订阅至关重要。

  1. 不需要
public MyViewModel()
{
    SomeService.SomePipelineModelingAsynchrony
        .Subscribe(...);
}

Pipelines modeling asynchrony可以依靠完成,因此订阅将通过OnComplete(或OnError)自动取消。

  1. 执行Dispose
public MyView()
{
    this.WhenAnyValue(x => x.ViewModel.SomeProperty)
        .Do(AssignValueToViewControl)
        .Subscribe();
}

现在,说的是“在this上附加PropertyChanged,并且告诉我ViewModel何时更改,然后将PropertyChanged附加到ViewModel,并告诉我SomeProperty何时更改。”这意味着视图模型具有对视图的引用,需要对其进行清理,否则视图模型将使视图保持存活。

  1. 性能提示
public MyView()
{
    // For a dependency property-based platform such as WPF and UWP
    this.WhenActivated(
        disposables =>
        {
            this.WhenAnyValue(x => x.ViewModel)
                .Where(x => x != null)
                .Do(PopulateFromViewModel)
                .Subscribe()
                .DisposeWith(disposables);
        });

    // For other platforms it can be simplified to the following
    this.WhenAnyValue(x => x.ViewModel)
        .Where(x => x != null)
        .Do(PopulateFromViewModel)
        .Subscribe()
}

private void PopulateFromViewModel(MyViewModel vm)
{
    // Assign values from vm to controls
}

比绑定属性更有效。如果ViewModel属性不会随时间变化,请一定使用此模式。WhenActivated部分对于基于依赖属性的平台(如情况2所述)非常重要,因为它会在每次视图失活时取消订阅。

  1. 不需要
// Should I dispose of the IDisposable that WhenActivated returns?
this.WhenActivated(disposables => { ... });

如果在视图中使用WhenActivated,那么何时释放它返回的可释放对象?必须将其存储在本地字段中,并使视图可释放。但是,谁来释放该视图?这就需要通过平台钩子来了解何时需要适当的时间来释放它 — 如果在虚拟化场景中重新使用该视图,这并不是意见容易的事。除此之外,我发现在虚拟机中的响应式代码特别容易导致大量可释放对象。将所有这些可释放对象存放起来并试图进行释放往往会使代码混乱,并迫使虚拟机本身称为可释放对象,从而更加混乱。性能是另一个需要考虑的因素,尤其是在Android上。 — Kent Boogaart @ You, I and ReactiveUI

特别感谢[@cabauman]创建并共享上面看到的ReactiveUI激活备忘录

原文 https://www.reactiveui.net/docs/handbook/when-activated

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注