MvvmCross
MvvmCross copied to clipboard
WindowController from Storyboard gets disposed
I'm still trying to figure out why it happens, but the code to instantiate from Storyboard is flawed in that after some activity, the created WindowController gets disposed. This seems to be resolved by adding a reference in presenters. Taking a look now.
Update. In the Presenter, there is a list of Windows. Is that to prevent premature disposal? Do I need one for the WindowController? I read that GC does not call "dispose" but in my case Dispose is being called.
The list of Windows is to support multiple windows active at a time. This allows you to show a sheet in a previously shown Window for example.
Maybe we can add a ConditionalWeakTable<NSWindow, NSViewController>?
I don't know why WindowController would get Disposed if Window is still alive. Still putzing around with this one. I do not see this in Playground, but I do see it in my "real" app where I do a database call which triggers the Disposal of my ToolbarWindow (MvxWindowController). Not sure why, but I suspect GC. It does not get disposed if I keep a reference of WindowControllers in the Presenter. Odd.
It looks like I can also keep the MvxWindowController from getting disposed by referring to it in my View, e.g.,
private ToolbarWindow _windowController;
public override void ViewDidAppear()
{
base.ViewDidAppear();
_windowController = WindowController;
var set = this.CreateBindingSet<WindowView, WindowViewModel>();
set.Apply();
GC.Collect();
}
What does ConditionalWeakTable do? Would it go in the Presenter?
I'm guessing that the problem is
windowController = (MvxWindowController)storyboard.InstantiateControllerWithIdentifier(attribute.WindowControllerName);
which perhaps does not tie the created window with the windowcontroller in a .NET way (in a way that prevents GC from disposing).
this seems to work
private ConditionalWeakTable<NSWindow, NSWindowController> weakTable = new ConditionalWeakTable<NSWindow, NSWindowController>();
protected virtual void ShowWindowViewController(
NSViewController viewController,
MvxWindowPresentationAttribute attribute,
MvxViewModelRequest request)
{
....
weakTable.Add(windowController.Window, windowController);
}
Any possible repercussions? Do I have to clear the weakTable when the Window is disposed?
ConditionalWeakTable will keep the value alive until the key is disposed. Yes, it would be placed in the Presenter.
It's not really necessary to cleanup values, it is done automatically. The drawback is that it consumes much more resources than a Dictionary for example, but I don't think that will be a problem here.
Looks like the problem is here again. If I add the forced GC.Collect() in the Playground.Mac.WindowView then the WindowController will be null or managed wrapper will be created from scratch again (so, WindowController itself will not be null, but all the connected outlets there will be null).
Steps to reproduce:
- get the project from test branch: https://github.com/snechaev/MvvmCross/tree/WindowControllerDisposed-issue2198
- run
Playground.Mac - click "Show window"
- in opened window click "Test" button. You will get the following
Testing window controller: WindowController is not null: True, ok Controller creation count: 1, ok Controller outlets is not null: True, ok - click the "GC" button to perform a forced GC.
- click the "Test" button again. You will get the following;
Testing window controller: WindowController is not null: False, not ok. <--- problem Controller creation count: 1, ok Controller outlets is not null: False, not ok. <--- problem
As I can see, the previous fix with the ConditionalWeakTable was removed in the rev. 9c1e34780176785f95a0ee8ae2d4d3fd98665237 by the @nmilcoff. @nmilcoff, do you remember the reasoning behind those changes?
In the current develop we explicitly store the list of the NSWindow-s, but as the NSWindow.windowController property is a weak reference, the NSWindowController will still be GC-ed.
@Cheesebaron, can you please reopen this issue?
managed wrapper will be created from scratch again (so,
WindowControlleritself will not be null, but all the connected outlets there will be null).
Just for the history: looks like this behavior was related to the runtime implementation where the native code can resolve the reference to the managed object which is in the finalizer queue: https://github.com/dotnet/macios/pull/23072