-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
I finally got an app up and going. ReactiveUI made a lot of it make more sense than it otherwise would have! However, it leaks memory like a... well...a lot. There are lots of images flying around in my app, so a VM or View that sticks around too long can do quite a bit of damage.
Using VS2013's memory analysis tools, I'm working with a Universal app, and using the memory profiler (from the Analyze menu). When I repeatedly navigate to a new VM from my "home page" when a command is "clicked":
SwitchPages
.Select(x => MeetingAddress)
.Where(x => IsMeeting(x))
.Subscribe(addr =>
{
Settings.LastViewedMeeting = addr;
HostScreen.Router.Navigate.Execute(new MeetingPageViewModel(HostScreen, ConvertToIMeeting(addr)));
});
and MeetingPageViewModel's xaml.cs file is:
public sealed partial class MeetingPage : Page, IViewFor<MeetingPageViewModel>
{
public MeetingPage()
{
this.InitializeComponent();
// Bind everything together we need.
this.Bind(ViewModel, x => x.MeetingTitle, y => y.MeetingTitle.Text);
}
/// <summary>
/// Stash the view model
/// </summary>
public MeetingPageViewModel ViewModel
{
get { return (MeetingPageViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(MeetingPageViewModel), typeof(MeetingPage), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (MeetingPageViewModel)value; }
}
}
where MeetingTitle is a TextBox (but it can be a TextBlock as well, and use OneWayBind, it doesn't matter), and the ViewModel is:
public class MeetingPageViewModel : ReactiveObject, IRoutableViewModel
{
public MeetingPageViewModel(IScreen hs, IMeetingRef mRef)
{
// Initial default values
HostScreen = hs;
_backing = "hi there";
}
/// <summary>
/// Track the home screen.
/// </summary>
public IScreen HostScreen { get; private set; }
/// <summary>
/// The meeting title
/// </summary>
public string MeetingTitle
{
get { return _backing; }
set { this.RaiseAndSetIfChanged(ref _backing, value); }
}
string _backing;
/// <summary>
/// Where we will be located.
/// </summary>
public string UrlPathSegment
{
get { return "/meeting"; }
}
}
I can then navigate to the screen and back 3 or 4 times, and with the analyzer after forcing a garbage collection, I'll see 3 or 4 instances of MeetingPageViewModel (and MeetingPage). If I remove the binding command, then it is correctly garbage collected. I can also bind to a back button (not shown here), but it doesn't matter if I do or not (I use InvokeCommand) - in all cases MeetingPage and MeetingPageViewModel are correctly collected. So it is just the Bind or OneWayBind that doesn't seem to work.
As a test I took the return from Bind and disposed of it. Of course, the binding was gone, but the VM and View were properly garbage collected.
I see this behavior both on Win10 (most recent build) and a fully patched version of Windows 8.
It is almost as if I should collect everything into a Composite Disposable object, and then dispose of that when the page is deactivated or similar. But my impression is I should not need to do that.
Is this a bug by me (in which case I will move it to stack overflow if we can figure out what I did wrong so others won't get stuck with this) or is this some sort of weird reference chain? BTW, I have a VS2013 diagnostic file which I can share if you want, which has a managed heap snap-shot after loading and unloading the page 3 times, so you can see the trace back to root (it wasn't illuminating for me! :-)).