ViewModels,消息和生命周期

时间:2013-09-25 13:47:18

标签: xamarin.android xamarin mvvmcross

我修改了@ slodge的一个示例,以解决我在ViewModels生命周期中遇到的问题。

我稍微修改了N26: https://github.com/csteeg/NPlus1DaysOfMvvmCross/tree/viewmodeldisposesample/N-26-Fraggle

此分支使用mvxmessenger插件向您显示出错的地方。 代码不漂亮,但显示你的不正确。您可以看到Id = 0的SubViewModel如何继续接收消息,即使它的视图早已消失。而且(在某些时候)HomeViewModel如何停止接收消息。

重现步骤(包括清理版本的调试输出):

启动应用

HomeViewModel:Warning:HomeViewModel 0 received: Created HomeViewModel0

点击按钮'1'

HomeViewModel:Warning:HomeViewModel 0 received: Created SubViewModel0 SubViewModel:Warning:SubViewModel 0 received: Created SubViewModel0 HomeViewModel:Warning:HomeViewModel 0 received: Created FirstViewModel0 SubViewModel:Warning:SubViewModel 0 received: Created FirstViewModel0 FirstViewModel:Warning:FirstViewModel 0 received: Created FirstViewModel0

点击返回

HomeViewModel:Warning:HomeViewModel 0 received: Destroyed FirstView for viewmodel 0 SubViewModel:Warning:SubViewModel 0 received: Destroyed FirstView for viewmodel 0 FirstViewModel:Warning:FirstViewModel 0 received: Destroyed FirstView for viewmodel 0 HomeViewModel:Warning:HomeViewModel 0 received: Destroyed SubFrag for viewmodel 0 SubViewModel:Warning:SubViewModel 0 received: Destroyed SubFrag for viewmodel 0 FirstViewModel:Warning:FirstViewModel 0 received: Destroyed SubFrag for viewmodel 0 HomeViewModel:Warning:HomeViewModel 0 received: Destroyed DubFrag for viewmodel 0 SubViewModel:Warning:SubViewModel 0 received: Destroyed DubFrag for viewmodel 0 FirstViewModel:Warning:FirstViewModel 0 received: Destroyed DubFrag for viewmodel 0 你可以在这里看到被破坏的视图,我希望视图模型可以随身携带

再次点击“1”按钮

HomeViewModel:Warning:HomeViewModel 0 received: Created SubViewModel1 SubViewModel:Warning:SubViewModel 0 received: Created SubViewModel1 FirstViewModel:Warning:FirstViewModel 0 received: Created SubViewModel1 SubViewModel:Warning:SubViewModel 1 received: Created SubViewModel1 HomeViewModel:Warning:HomeViewModel 0 received: Created FirstViewModel1 SubViewModel:Warning:SubViewModel 0 received: Created FirstViewModel1 FirstViewModel:Warning:FirstViewModel 0 received: Created FirstViewModel1 SubViewModel:Warning:SubViewModel 1 received: Created FirstViewModel1 FirstViewModel:Warning:FirstViewModel 1 received: Created FirstViewModel1

在这里,您看到,subviewmodel 0仍在接收消息。 我可以告诉它应该停止向未附加的视图模型发送消息吗? 或者视图模型可以知道没有附加

现在,当你继续重复这些步骤很长一段时间,在模拟器中说15次, 一些视图模型将停止接收消息(我猜他们是垃圾收集)。 奇怪的是,其中一个观点是 HomeViewModel ! HomeView永远不会被破坏,但homeviewmdoel停止接收消息, 因此,如果您的应用需要

,则能够相应地更新视图

2 个答案:

答案 0 :(得分:5)

我遇到了类似的情况,MvxMessages仍然被接收并且由不再附加到视图的视图模型执行。

我的解决方案是将以下内容添加到基本视图模型中:

  • 订阅方法
  • 取消订阅操作列表(由Subscribe方法添加)
  • 一个UnsubscribeAll方法

在Android活动OnDestoy中,我调用了viewmodel的UnsubscribeAll。

(作为奖励,因为在取消订阅操作中引用了令牌,我不需要保留其他列表)

BaseViewModel:

    #region Messenger

    /// <summary>
    /// Must set the Messenger object before doing any subscribing
    /// </summary>
    public IMvxMessenger Messenger { get; set; }

    private readonly object _messengerLock = new Object();

    private List<Action> _unsubscribeActions;

    /// <summary>
    /// Subscribe to a message, and store in a list so can be unsubscribed automatically later
    /// </summary>
    /// <typeparam name="TMessage"></typeparam>
    /// <param name="deliveryAction"></param>
    public void Subscribe<TMessage>(Action<TMessage> deliveryAction) where TMessage: MvxMessage
    {
        var messenger = Messenger;
        if (messenger == null) { return; }

        var token = messenger.Subscribe<TMessage>(deliveryAction);

        Action unsubscriber = delegate()
        {
            messenger.Unsubscribe<TMessage>(token);
        };

        lock (_messengerLock)
        {
            if (_unsubscribeActions == null)
            {
                _unsubscribeActions = new List<Action>();
            }
            _unsubscribeActions.Add(unsubscriber);
        }
    }

    /// <summary>
    /// Unsubscribe to all messages which have been previously subscribed to
    /// </summary>
    public void UnsubscribeAll()
    {
        if (_unsubscribeActions == null) { return; }
        lock (_messengerLock)
        {
            foreach (var a in _unsubscribeActions)
            {
                a();
            }
            _unsubscribeActions = null;
        }
    }

    #endregion

BaseActivity:

    protected override void OnDestroy()
    {
        var vm = ViewModel as ViewModel.BaseViewModel;
        if (vm != null) { vm.UnsubscribeAll(); }
        base.OnDestroy();
    }

答案 1 :(得分:4)

MvvmCross v3不会将ViewModel暴露给任何View生命周期事件,如ViewDidAppear / Disappear,OnNavigatedTo / From,OnPause / OnResume / OnDestroy。

原因是:

  • 早期的MvvmCross版本试图这样做,并且在ViewDidUnload中发现了iOS更改并且一般都支持iOS(当视图已经过时很难解决)
  • 当视图不是页面 - 而是选项卡,弹出窗口,拆分视图,弹出窗口等 - 然后很难支持和混淆开发人员

相反,MvvmCross v3使用垃圾收集来整理ViewModel。为了帮助这个Mvx,总是使用ViewModel-to-View和MessageHub-to-ViewModels中的WeakReference。接下来的一般理念是:

  • 唯一能够在View上保留强大参考的是操作系统。
  • 唯一能够在ViewModel上保留强大引用的是它的View。

话虽如此,如果GarbageCollection对您的应用程序来说不够及时,那么MvvmCross确实允许您使用新功能扩展ViewModel。例如,如果您愿意,可以轻松添加新的IViewLifecycleAware界面。这很容易做到,但是一旦完成,那么您的应用程序有责任确保从您支持的每个平台上的相应View事件/覆盖中调用接口。

关于这个话题还有一点:


对于HomeViewModel停止接收邮件的具体问题,我认为这是因为您没有存储订阅令牌。

因为默认情况下MvvmCross信使使用弱引用,所以必须存储订阅令牌 - 当处理该令牌或垃圾收集时,订阅将被取消订阅。

所以你的代码:

public class HomeViewModel
    : MvxViewModel
{
    public static int IdCounter = 0;
    public int Id = IdCounter++;
    public HomeViewModel()
    {

        var messenger = Mvx.Resolve<IMvxMessenger>();
        messenger.Subscribe<JustAMessage>(OnMessage);
        messenger.Publish(new JustAMessage(this) { Message = "Created HomeViewModel" + Id });
    }

    // ...

需要:

public class HomeViewModel
    : MvxViewModel
{
    public static int IdCounter = 0;
    public int Id = IdCounter++;

    private IDisposable _token;

    public HomeViewModel()
    {

        var messenger = Mvx.Resolve<IMvxMessenger>();
        _token = messenger.Subscribe<JustAMessage>(OnMessage);
        messenger.Publish(new JustAMessage(this) { Message = "Created HomeViewModel" + Id });
    }

    // ...

有更多相关内容: