Bug 34375 - RemoveFromSuperView is not called on subviews, behavior different from Apple AppKit implementation
Summary: RemoveFromSuperView is not called on subviews, behavior different from Apple ...
Status: RESOLVED ANSWERED
Alias: None
Product: Xamarin.Mac
Classification: Desktop
Component: Library (Xamarin.Mac.dll) ()
Version: 2.0.2
Hardware: PC Mac OS
: --- normal
Target Milestone: ---
Assignee: Chris Hamons
URL:
Depends on:
Blocks:
 
Reported: 2015-09-28 20:27 UTC by Kristin Lee
Modified: 2015-09-29 14:16 UTC (History)
2 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Sample code built in Xcode and Xamarin Studio for comparison. (57.91 KB, application/zip)
2015-09-28 20:27 UTC, Kristin Lee
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and 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 Links.

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.

Related Links:
Status:
RESOLVED ANSWERED

Description Kristin Lee 2015-09-28 20:27:33 UTC
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.
Comment 1 Chris Hamons 2015-09-29 11:10:02 UTC
This is a much simplified example showing the issue at hand.

https://gist.github.com/chamons/35cddf3648a966e490cc
Comment 2 Chris Hamons 2015-09-29 14:16:59 UTC
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:

http://www.mono-project.com/docs/advanced/garbage-collector/sgen/#conservative-scanning

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.