Is this a bug in MonoTouch GC? - xamarin.ios

Note: I've created a simple project—you can see how switching types between UIButton and CustomButton in storyboard changes GC behavior.
I'm trying to get my head wrapped around MonoTouch garbage collector.
The issue is similar to the one fixed in MT 4.0, however with inherited types.
To illustrate it, consider two view controllers, parent and child.
Child's view contains a single UIButton that writes to console on tap.
Controller's Dispose method throws an exception so it's hard to miss.
Here goes child view controller:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
sayHiButton.TouchUpInside += (sender, e) =>
SayHi();
}
}
void SayHi()
{
Console.WriteLine("Hi");
}
protected override void Dispose (bool disposing)
{
throw new Exception("Hey! I've just been collected.");
base.Dispose (disposing);
}
Parent view controller just presents child controller and sets a timer to dismiss it and run GC:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
var child = (ChildViewController)Storyboard.InstantiateViewController("ChildViewController");
NSTimer.CreateScheduledTimer(2, () => {
DismissViewController(false, null);
GC.Collect();
});
PresentViewController(child, false, null);
}
If you run this code, it predictably crashes inside ChildViewController.Dispose() called from its finalizer because child controller has been garbage collected. Cool.
Now open the storyboard and change button type to CustomButton. MonoDevelop will generate a simple UIButton subclass:
[Register ("CustomButton")]
public partial class CustomButton : UIButton
{
public CoolButton (IntPtr handle) : base (handle)
{
}
void ReleaseDesignerOutlets()
{
}
}
Somehow changing the button type to CustomButton is enough to trick garbage collector into thinking child controller is not yet eligible for collection.
How is that so?

This is an unfortunate side-effect of MonoTouch (who is garbage collected) having to live in a reference counted world (ObjectiveC).
There are a few pieces of information required to be able to understand what's going on:
For every managed object (derived from NSObject), there is a corresponding native object.
For custom managed classes (derived from framework classes such as UIButton or UIView), the managed object must stay alive until the native object is freed [1]. The way it works is that when a native object has a reference count of 1, we do not prevent the managed instance from getting garbage collected. As soon as the reference count increases above 1, we prevent the managed instance from getting garbage collected.
What happens in your case is a cycle, which crosses the MonoTouch/ObjectiveC bridge and due to the above rules, the GC can't determine that the cycle can be collected.
This is what happens:
Your ChildViewController has a sayHiButton. The native ChildViewController will retain this button, so its reference count will be 2 (one reference held by the managed CustomButton instance + one reference held by the native ChildViewController).
The TouchUpInside event handler has a reference to the ChildViewController instance.
Now you see that the CustomButton instance will not be freed, because its reference count is 2. And the ChildViewController instance will not be freed because the CustomButton's event handler has a reference to it.
There are a couple of ways to break the cycle to fix this:
Detach the event handler when you no longer need it.
Dispose the ChildViewController when you no longer need it.
[1] This is because a managed object may contain user state. For managed objects which are mirroring a corresponding native object (such as the managed UIView instance) MonoTouch knows that the instance can not contain any state, so as soon as no managed code has a reference to the managed instance, the GC can collect it. If a managed instance is required at a later stage, we just create a new one.

Related

Is reference counting method no longer used for Xamarin.iOS in Xamarin.Forms 2.4.0 and above nugget?

I have been developing Xamarin since XF 1.0. Memory leak was a huge problem from XF 1.0 till now XF 2.4. I have always been implementing Dispose Pattern in all my ContentPage and ViewModel classes to make sure Event handlers, Message Subscribers, object references are all removed and set to null either in OnDisappearing() or in Dispose() function of each class.
Here is something very intriguing. After I upgraded my project to Xamarin.Forms 2.4.0.280 from 2.3.3.180. I found out this type of code that used to create memory leak in Xamarin.iOS no longer occur.
Code Example:
public partial class ContactUsPage : ContentPage
{
public ContactUsPage ()
{
InitializeComponent ();
}
protected override void OnAppearing ()
{
//Lets say I have button named "btn" declared in Xaml page
btn.Clicked += OnButtonClicked
MessagingCenter.Subscribe<CoreMessenger, string>(this, "AMessageType", (sender, arg) =>
{
//some code
});
}
protected override void OnDisappearing ()
{
//btn.Clicked -= OnButtonClicked
//MessagingCenter.Unsubscribe<CoreMessenger, string>(this, "AMessageType");
}
}
Prior to Xamarin.Forms 2.4, If we Push and Pop this page as a Modal multiple time, we will see multiple instances of this page persist in memory and will never be Garbage Collected due to Event Handler and Subscriber are not removed and they created object references pointing back to the ContactUsPage instances, unless we uncomment the code in OnDisappearing () function.
The above code no longer creates memory leak in Xamarin iOS when compiled in Xamarin.Forms 2.4.0.280, although it is clearly leaking memory according to my knowledge of how reference counting garbage collection works. Did Xamarin change the GC method for Xamarin.iOS? So it is doing mark-and-sweep like Xamarin.Android now?
Can someone tell me what changed and why?
Thanks a ton!
Have a look at the commit history for the MessagingCenter.
https://github.com/xamarin/Xamarin.Forms/commits/master/Xamarin.Forms.Core/MessagingCenter.cs
I believe you are seeing the effects of the commit on Jan 03, 2017. At least in terms of your example above using MessagingCenter.

XNA 4 GraphicsDevice

From my few years of experience programming in graphics, one thing that I have learned is that you should never pass in a reference to a graphics context to an object and operate on it for the duration of the program (JOGL explicitly states this). A context can be invalidated when something such as the graphics device (GPU) is reset, turned off, or some other weird thing happens.
I have recently delved back into programming in XNA 4.0, and one of my projects involves objects needing to know about the size of the window/viewport, when the window is resized, and when dynamic buffers have lost their content (requiring the buffers to be rebuilt on a possibly invalidated GraphicsDevice). Instead of passing in the GraphicsDevice and GameWindow to numerous methods in the update phase or for Disposal, I have opted to pass them into constructors. For example:
public Camera(GameWindow w, GraphicsDeviceManager m) {
// ... Yada-yada matrices
gdm = m;
window = w;
window.ClientSizeChanged += OnWindowResize;
}
public void Dispose() {
window.ClientSizeChanged -= OnWindowResize;
window = null;
gdm = null;
}
// Control Logic ...
public void OnWindowResize(object Sender, EventArgs args) {
Vector2 s = new Vector2(gdm.GraphicsDevice.Viewport.TitleSafeArea.Width, gdm.GraphicsDevice.Viewport.TitleSafeArea.Height);
// Recalculate Projection ...
}
Is it safe to do something like this, or is something happening in the background that I need to be aware of?
I solved this problem in my current game project by running the game as a singleton, which makes it available in a static context within the namespace. Game.Instance.graphicsDevice will always point to the current graphics device object, even if the context has changed. XNA raises various events when the context is invalidated/changed/reset/etc., and you can reload/re-render things and resize buffers as needed by hooking in to these events.
Alternatively, you could pass GraphicsDevice with the ref keyword, which might be a quick, drop-in fix by simply being the same reference as the original caller, assuming that caller that instantiated your objects either has the original reference object or had the GraphicsDevice passed to it with ref as well.

Does every monotouch UIKit variable need to be at class scope?

In this Xamarin forum post, Xamarin admin Clancey says:
But as a general rule, anything that has that type of interaction say
a UIButton, you really need to keep a reference to it.
For example:
//This can will blow up
public override ViewDidLoad()
{
var myButton = new UIButton();
myButton.Tapped += delegate{
//do something
};
View.AddSubview(myButton);
}
This is because myButton goes out of scope and is GC'ed, but the underlying iOS button handle is still alive, and if ObjectiveC calls back to Mono with that button handle, the app crashes. So you need to make myButton a class level variable, so it stays alive.
My question is: what about other kinds of UIKit objects, like UIImage? Suppose I do this:
var image = new UIImage(sFilename);
ctlImageView = new UIImageView(image);
this.View.Add(ctlImageView);
ctlImageView is at class scope, but image isn't. Can this cause the same kind of memory leak, and does image also need to be at class scope? Or are UIView objects special in this regard?
You don't need to keep a reference to the UIImage because the UIImageView will hold one for you and clean it up when it is disposed.

Portable Class Library and ObservableCollection, updating UI Thread

I'm not very experienced with this topic so forgive me if this isn't very clear.
I've created a Portable Class Library that has an ObservableCollection of Sections, and each secion has an ObservableCollection of Items.
Both of these collections are bound to the UI of separate Win8 and WP8 apps.
I'm trying to figure out the correct way to populate these collections correctly so that the UI gets updated from the PCL class.
If the class was inside the win8 project I know I could do something like Dispatcher.BeginInvoke, but this doesn't translate to the PCL, nor would I be able to reuse that in the WP8 project.
In this thread (Portable class library equivalent of Dispatcher.Invoke or Dispatcher.RunAsync) I discovered the SynchroniationContext class.
I passed in a reference to the main app's SynchroniationContext, and when I populate the sections I can do so because it's only the one object being updated:
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the CollectionChanged event on the current thread
UpdateSections(sections);
}
else
{
// Post the CollectionChanged event on the creator thread
_synchronizationContext.Post(UpdateSections, sections);
}
However, when I try to do the same thing with articles, I have to have a reference to both the section AND the article, but the Post method only allows me to pass in a single object.
I attempted to use a lambda expression:
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the CollectionChanged event on the current thread
section.Items.Add(item);
}
else
{
// Post the CollectionChanged event on the creator thread
_synchronizationContext.Post((e) =>
{
section.Items.Add(item);
}, null);
}
but I'm guessing this is not correct as I'm getting an error about being "marshalled for a different thread".
So where am I going wrong here? how can I update both collections correctly from the PCL so that both apps can also update their UI?
many thanks!
Hard to say without seeing the rest of the code but I doubt is has anything to do with Portable Class Libraries. It would be good to see the details about the exception (type, message and stack trace).
The way you call Post() with more than argument looks correct. What happens if you remove the if check and simply always go through SynchronizationContext.Post()?
BTW: I don't explicitly pass in the SynchronizationContext. I assume that the ViewModel is created on the UI Thread. This allows me to capture it like this:
public class MyViewModel
{
private SynchronizationContext _context = SynchronizationContext.Current;
}
I would recommend that at least in your ViewModels, all publicly observable state changes (ie property change notifications and modifications to ObservableCollections) happen on the UI thread. I’d recommend doing the same thing with your model state changes, but it might make sense to let them make changes on different threads and marshal those changes to the UI thread in your ViewModels.
To do this, of course, you need to be able to switch to the UI thread in portable code. If SynchronizationContext isn’t working for you, then just create your own abstraction for the dispatcher (ie IRunOnUIThread).
The reason you were getting the "marshalled on a different thread" error is that you weren't passing the item to add to the list as the "state" object on the Post(action, state) method.
Your code should look like this:
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the CollectionChanged event on the current thread
section.Items.Add(item);
}
else
{
// Post the CollectionChanged event on the creator thread
_synchronizationContext.Post((e) =>
{
var item = (YourItemnType) e;
section.Items.Add(item);
}, item);
}
If you make that change, your code will work fine from a PCL.

Why can't MonoTouch GC kill managed objects with refcount > 1?

I think I'm getting close to understanding how Mono GC and ObjC ref counting live together.
The way it works is that when a native object has a reference count of 1, we do not prevent the managed instance from getting garbage collected. As soon as the reference count increases above 1, we prevent the managed instance from getting garbage collected.
This is because a managed object may contain user state. For managed objects which are mirroring a corresponding native object (such as the managed UIView instance) MonoTouch knows that the instance can not contain any state, so as soon as no managed code has a reference to the managed instance, the GC can collect it. If a managed instance is required at a later stage, we just create a new one.
So if I create a CustomButton that inherits UIButton, add it as subview to my View, let the managed reference slip out of scope and then run GC, this managed CustomButton still won't be eligible for collection.
Why can't it be collected? Of course it may have managed state like properties, but if there is no link to it from managed objects, who cares about this state? It may as well just disappear, why can't it?
I'm thinking of one possible reason: subscribing to CustomButton events won't keep it alive for the GC so when the object gets collected, events stop firing. This would perhaps result in unexpected behavior.
Is this correct? Are there other reasons for keeping the managed object alive even if no one links it?
Why can't it be collected? Of course it may have managed state like properties, but if there is no link to it from managed objects, who cares about this state? It may as well just disappear, why can't it?
Native code might have references to the object, which may cause the object to resurface to managed code again later.
I believe a code sample would illustrate what would happen:
class MyView : UIView {
public string ImportantSecret;
}
class AppDelegate : UIApplicationDelegate {
UIViewController vc;
public override bool FinishedLaunching (UIApplication app,
NSDictionary options)
{
var myView = new MyView ();
myView.ImportantSecret = "MonoTouchRocks";
vc = new UIViewController ();
vc.View = new UIView ();
vc.View.AddSubView (myView);
// When this method returns the only place where myView is referenced
// is from inside the *native* Subviews collection.
BeginInvokeOnMainThread (() =>
{
Console.WriteLine (((MyView) vc.Subviews [0]).ImportantSecret);
// If the MyView instance was garbage collected and recreated
// automatically at this point, ImportantSecret would be null.
});
}
}
Important: this code is just to illustrate the reason why the GC can't collect managed objects which may have state. This particular sample would actually not forget the important secret, since the Subviews array is automatically cached in managed code - but this is not generally true.

Resources