Notice (2018-05-24): bugzilla.xamarin.com is now in
Please join us on
Visual Studio Developer Community and in the
Mono organizations on
GitHub to continue tracking issues. Bugzilla will remain
available for reference in read-only mode. We will continue to work
on open Bugzilla bugs, copy them to the new locations
as needed for follow-up, and add the new items under Related
Our sincere thanks to everyone who has contributed on this bug
tracker over the years. Thanks also for your understanding as we
make these adjustments and improvements for the future.
Please create a new report on
Developer Community or GitHub with
your current version information, steps to reproduce, and relevant error
messages or log files if you are hitting an issue that looks similar to
this resolved bug and you do not yet see a matching new report.
Created attachment 13112 [details]
Sample code built in Xcode and Xamarin Studio for comparison.
In the Apple AppKit implementation, when myCustomView.RemoveFromSuperview() is called, myCustomView's subviews will each get called for "RemoveFromSuperview". This allow subviews to have a chance to perform cleanup, such as breaking any circular references to prevent memory leaks.
When I build the same scenario in Xamarin Studio, the behavior is different from Apple AppKit's implementation. Only myCustomView gets RemoveFromSuperview called. None of its subviews get that message.
See attached samples built in Xcode vs Xamarin Studio for comparison.
This is a much simplified example showing the issue at hand.
Alright, I did a bunch of digging and now can give you answers, and a work around, but no fix.
First off, why is this working in Xcode/Objective-C. If you take a look at the stack trace, you'll get:
frame #0: 0x0000000100001026 NSViewChainTest`-[CustomView removeFromSuperview](self=0x0000610000140fd0, _cmd="removeFromSuperview") + 54 at AppDelegate.m:23
* frame #1: 0x00007fff874b2d99 AppKit`-[NSView removeFromSuperviewWithoutNeedingDisplay] + 38
frame #2: 0x00007fff873eb698 AppKit`-[NSView _finalizeWithReferenceCounting] + 1000
frame #3: 0x00007fff873eb27e AppKit`-[NSView dealloc] + 151
The parent view is being deallocated right now, and that is calling removeFromSuperviewWithoutNeedingDisplay which is what calling removeFromSuperview on your child subviews.
Ok, now why isn't that happening. From what I can tell during my research, one instance of the parent view is being left in memory, most likely due to the conservative scanning by the collector. You can read about it here:
In many cases, after sufficient running time, the unmanaged state/registers change such that we no longer see the possibility of pointers to managed objects and can collect them.
However, in any garbage collected language, you shouldn't need to worry about exactly when an objects dies unless you do one of two things:
- Create 10s of thousands of NSViews dynamically, and cycle them in and out of memory
- Depend on NSView lifecycle for other resource cleanup.
I wouldn't suggest either of these.
But ignoring that for a minute, even if mono had a precise gc right now, you still are using a garbage collector, which means that you shouldn't depend on this behavior from AppKit. Imagine this sequence:
- Remove parent from superview
- We notice parent is now dead (the thing that isn't happening now, in this example)
- We then wait for the next GC cycle to reap the parent
- Then dealloc in native happens and you get notifications
The timing will inherently be different between objective-c and C#. Period. Full Stop.
You have two options in dealing with this:
- On top level objects, call dispose as soon as you remove them from the superview. This will cause dealloc to fire right away, which triggers it's children to get the remove from superview call. However, this is troublesome once you have a nested tree with multiple levels. (Do you dispose the subviews as well, recursively? I hope you don't mess it up, else you'll get exceptions when you use a disposed object).
- Stop relying on what, I personally consider, is bad behavior by AppKit. Calling selectors during dealloc is bad form.
Sorry for not the answer you were most likely were hoping for.