【问题标题】:ViewModels, messages and lifetimeViewModel、消息和生命周期
【发布时间】:2023-03-28 02:33:01
【问题描述】:

我已针对我的 ViewModels 生命周期中的问题修改了 @slodge 的一个示例。

我对 N26 做了一些修改: https://github.com/csteeg/NPlus1DaysOfMvvmCross/tree/viewmodeldisposesample/N-26-Fraggle

这个分支使用 mvxmessenger 插件能够告诉你哪里出了问题。 代码不是很漂亮,但表明你 wat 不正确。您可以看到 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

在这里,子视图模型 0 仍在接收消息。 我可以以某种方式告诉它应该停止向未附加的视图模型发送消息吗? 或者视图模型是否可以知道未附加

现在,当您继续重复这些步骤相当长的一段时间时,比如在模拟器中重复 15 次, 一些视图模型将停止接收消息(我猜它们是垃圾收集的)。 奇怪的是,其中一个视图是 HomeViewModel! HomeView 永远不会被销毁,但是 homeviewmdoel 会停止接收消息, 因此,如果您的应用需要,nog 能够相应地更新视图

【问题讨论】:

    标签: xamarin.android xamarin mvvmcross


    【解决方案1】:

    我遇到了类似的情况,MvxMessages 仍然被不再附加到视图的视图模型接收和操作。

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

    • 订阅方法
    • 取消订阅操作列表(由订阅方法添加)
    • 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
    

    基础活动:

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

    【讨论】:

    • 谢谢你,这在几天没有找到解决方案后挽救了我的生命。再次,我的视图不再附加到我的视图模型,但消息被发送到哪里。谢谢!!!
    【解决方案2】:

    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 的WeakReferences。遵循的一般理念是:

    • 在视图上保持强引用的唯一因素是操作系统。
    • 在 ViewModel 上保持强引用的唯一事物就是它的 View。

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

    关于这个主题的更多内容:


    对于您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 });
        }
    
        // ...
    

    关于此的更多信息:

    【讨论】:

    • 感谢 Stuart 的出色回答,确实忘记存储令牌:doh!现实世界应用程序的问题更深一些: - 片段中的项目列表 - 替换片段 - 再次加载列表 列表通过 mvxadapter 绑定到 ObservableCollection 第一个加载片段的适配器仍然存在只要 viewmodel 还活着(也许甚至在之后,但只要 viewmodel 在那里,它仍然附加到 viewmodel 的 collectionchanged 事件)。当集合更改(通过我的数据层的事件)时,它会在 notifycollectionchanged 中崩溃
    • 一直存在的适配器,在阅读此问题后被发现!:github.com/slodge/MvvmCross/issues/390?source=cc 我使用了带有嵌入式 mvxlistview 的膨胀布局,然后在该列表视图上设置了 MvxLayoutDrivenAdapter
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-28
    • 1970-01-01
    • 1970-01-01
    • 2010-10-09
    • 1970-01-01
    • 2021-06-20
    • 1970-01-01
    相关资源
    最近更新 更多