Smr Smr - 5 months ago 21
C# Question

Xamarin memory reclaim issues

I have used Xamarin for a while now, and I have encountered a very strange issue.

I am able to crash a very simple two screens app. On the first screen, I have a

UIButton
with the
TouchUpInside
event. On the second, I have a
UIImageView
with attached image (from local file).

All I have to do is to move back and forward between those two View-Controllers, all the time.

When I attached XCode's Instruments with Activity Monitor on, I noticed that my simple app reaches ~100MB of memory before the memory gets reclaimed, then its usage drops to ~15MB.

But when I loop navigation long enough, memory reaches over 140MB and the app crashes. I have discovered this behaviour while working on a more complex app. Of course, I am taking all available precautions:


  • unsubscribing events on ViewWillDisappear

  • nulling the delegates and so on.



Basicly, in my complex app, I have overriden the
Dispose
method in my base class for all
UIViewControllers
, and I can see that the
Dispose
method is called with
disposing == false
on every view controller that was displayed. However, the memory usage doesn't drop.

What is wrong with it?

I would like to point out few things:


  • My Xamarin Studio is up to date,

  • Crashes appeared while I was testing applications in debug mode on iPhone 3GS iOS 6.1.3

  • In simple appplication, the image was a 1024x1024 JPG file.



Herewith some code sample:

public partial class SimpleTestViewController : UIViewController
{
private UIButton button;
public SimpleTestViewController () : base ("SimpleTestViewController", null) { }

public override void DidReceiveMemoryWarning ()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning ();

// Release any cached data, images, etc that aren't in use.
}

public override void ViewDidLoad ()
{
base.ViewDidLoad ();
button = new UIButton (new RectangleF(0, 100, 100, 50));
button.BackgroundColor = UIColor.Red;
button.TouchUpInside += (sender, e) => {
this.NavigationController.PushViewController(new SecondView(), true);
};
this.Add (button);

// Perform any additional setup after loading the view, typically from a nib.
}
}

public partial class SecondView : UIViewController
{
private UIImageView _imageView;

public SecondView () : base ("SecondView", null) { }

public override void DidReceiveMemoryWarning ()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning ();

// Release any cached data, images, etc that aren't in use.
}

public override void ViewDidLoad ()
{
base.ViewDidLoad ();

_imageView = new UIImageView (new RectangleF(0,0, 200, 200));
_imageView.Image = UIImage.FromFile ("Images/image.jpg");
this.Add (_imageView);
// Perform any additional setup after loading the view, typically from a nib.
}

protected override void Dispose (bool disposing)
{
System.Diagnostics.Debug.WriteLine("Disposing " +this.GetType()
+ " hash code " + this.GetHashCode()
+ " disposing flag "+disposing);
base.Dispose (disposing);
}
}

Answer Source

You're creating an instance of the button / image and storing them in a backing field, and you're not calling Dispose on them in your controller's Dispose. The Controller instantiated them, so you have to clear them down.

In the example above, you are also not unwiring the buttons TouchUpInside event. I'd suggest not using a lambda for this, and actually create a method for it makes it easier to detach later on.

TouchUpInside -= this.Method; 

In addition, you're not removing the image from the view you added it to.

I know you say you're doing these things in the view, it will dissapear, but that's not happening in your example code. Can you provide a fully worked example with these basics taken care of?