Xamarin iOS Memory Leaks Everywhere

Xamarin iOS memory leaks everywhere

I used the below extension methods to solve these memory leak issues. Think of Ender's Game final battle scene, the DisposeEx method is like that laser and it disassociates all views and their connected objects and disposes them recursively and in a way that shouldn't crash your app.

Just call DisposeEx() on UIViewController's main view when you no longer need that view controller. If some nested UIView has special things to dispose, or you dont want it disposed, implement ISpecialDisposable.SpecialDispose which is called in place of IDisposable.Dispose.

NOTE: this assumes no UIImage instances are shared in your app. If they are, modify DisposeEx to intelligently dispose.

    public static void DisposeEx(this UIView view) {
const bool enableLogging = false;
try {
if (view.IsDisposedOrNull())
return;

var viewDescription = string.Empty;

if (enableLogging) {
viewDescription = view.Description;
SystemLog.Debug("Destroying " + viewDescription);
}

var disposeView = true;
var disconnectFromSuperView = true;
var disposeSubviews = true;
var removeGestureRecognizers = false; // WARNING: enable at your own risk, may causes crashes
var removeConstraints = true;
var removeLayerAnimations = true;
var associatedViewsToDispose = new List<UIView>();
var otherDisposables = new List<IDisposable>();

if (view is UIActivityIndicatorView) {
var aiv = (UIActivityIndicatorView)view;
if (aiv.IsAnimating) {
aiv.StopAnimating();
}
} else if (view is UITableView) {
var tableView = (UITableView)view;

if (tableView.DataSource != null) {
otherDisposables.Add(tableView.DataSource);
}
if (tableView.BackgroundView != null) {
associatedViewsToDispose.Add(tableView.BackgroundView);
}

tableView.Source = null;
tableView.Delegate = null;
tableView.DataSource = null;
tableView.WeakDelegate = null;
tableView.WeakDataSource = null;
associatedViewsToDispose.AddRange(tableView.VisibleCells ?? new UITableViewCell[0]);
} else if (view is UITableViewCell) {
var tableViewCell = (UITableViewCell)view;
disposeView = false;
disconnectFromSuperView = false;
if (tableViewCell.ImageView != null) {
associatedViewsToDispose.Add(tableViewCell.ImageView);
}
} else if (view is UICollectionView) {
var collectionView = (UICollectionView)view;
disposeView = false;
if (collectionView.DataSource != null) {
otherDisposables.Add(collectionView.DataSource);
}
if (!collectionView.BackgroundView.IsDisposedOrNull()) {
associatedViewsToDispose.Add(collectionView.BackgroundView);
}
//associatedViewsToDispose.AddRange(collectionView.VisibleCells ?? new UICollectionViewCell[0]);
collectionView.Source = null;
collectionView.Delegate = null;
collectionView.DataSource = null;
collectionView.WeakDelegate = null;
collectionView.WeakDataSource = null;
} else if (view is UICollectionViewCell) {
var collectionViewCell = (UICollectionViewCell)view;
disposeView = false;
disconnectFromSuperView = false;
if (collectionViewCell.BackgroundView != null) {
associatedViewsToDispose.Add(collectionViewCell.BackgroundView);
}
} else if (view is UIWebView) {
var webView = (UIWebView)view;
if (webView.IsLoading)
webView.StopLoading();
webView.LoadHtmlString(string.Empty, null); // clear display
webView.Delegate = null;
webView.WeakDelegate = null;
} else if (view is UIImageView) {
var imageView = (UIImageView)view;
if (imageView.Image != null) {
otherDisposables.Add(imageView.Image);
imageView.Image = null;
}
} else if (view is UIScrollView) {
var scrollView = (UIScrollView)view;
// Comment out extension method
//scrollView.UnsetZoomableContentView();
}

var gestures = view.GestureRecognizers;
if (removeGestureRecognizers && gestures != null) {
foreach(var gr in gestures) {
view.RemoveGestureRecognizer(gr);
gr.Dispose();
}
}

if (removeLayerAnimations && view.Layer != null) {
view.Layer.RemoveAllAnimations();
}

if (disconnectFromSuperView && view.Superview != null) {
view.RemoveFromSuperview();
}

var constraints = view.Constraints;
if (constraints != null && constraints.Any() && constraints.All(c => c.Handle != IntPtr.Zero)) {
view.RemoveConstraints(constraints);
foreach(var constraint in constraints) {
constraint.Dispose();
}
}

foreach(var otherDisposable in otherDisposables) {
otherDisposable.Dispose();
}

foreach(var otherView in associatedViewsToDispose) {
otherView.DisposeEx();
}

var subViews = view.Subviews;
if (disposeSubviews && subViews != null) {
subViews.ForEach(DisposeEx);
}

if (view is ISpecialDisposable) {
((ISpecialDisposable)view).SpecialDispose();
} else if (disposeView) {
if (view.Handle != IntPtr.Zero)
view.Dispose();
}

if (enableLogging) {
SystemLog.Debug("Destroyed {0}", viewDescription);
}

} catch (Exception error) {
SystemLog.Exception(error);
}
}

public static void RemoveAndDisposeChildSubViews(this UIView view) {
if (view == null)
return;
if (view.Handle == IntPtr.Zero)
return;
if (view.Subviews == null)
return;
view.Subviews.ForEach(RemoveFromSuperviewAndDispose);
}

public static void RemoveFromSuperviewAndDispose(this UIView view) {
view.RemoveFromSuperview();
view.DisposeEx();
}

public static bool IsDisposedOrNull(this UIView view) {
if (view == null)
return true;

if (view.Handle == IntPtr.Zero)
return true;;

return false;
}

public interface ISpecialDisposable {
void SpecialDispose();
}

Memory leaks in Xamarin.Forms UWP

So I think my statement is correct:

If you navigate to and from a page and dispose of the page and all its
components and call GC.Collect(). Then the page and all its
components should be gone from memory.

I found this article that did exactly that:
https://www.xamarinhelp.com/tracking-memory-leaks-xamarin-profiler/

"You will want to press the snapshot button once your app has loaded,
to get a baseline. Then, lets navigate to the second page, and press
back again. I would create another snapshot now. Then I might keep on
doing the navigation a few more times and pressing snapshot again."

"In our example, we went back and forth between the MainPage and
SecondPage. We should only see one new SecondPage at most between
snapshots, but here we see that multiple were created and are still
active."

When adding observer to the UIView causes memory leak in xamarin iOS

I resolved the issue by disposing the NSObject as like in the code snippet.

    private NSObject deviceRotatedObserver;
this.deviceRotatedObserver = Foundation.NSNotificationCenter.DefaultCenter.AddObserver(new NSString("UIDeviceOrientationDidChangeNotification"), this.DeviceRotated);

protected override void Dispose(bool disposing)
{
if (this.deviceRotatedObserver != null)
{
this.deviceRotatedObserver.Dispose();
this.deviceRotatedObserver = null;
}
}

Monotouch (Xamarin.iOS) memory leaks

What happens is that there is a circular dependency (with the event handlers) that crosses the native-managed boundary, and currently the Xamarin.iOS runtime/GC is not able to detect this.

You've already found the fix for this problem: break the circular dependency by removing event handlers.

Here is a video explaining the situation in detail: http://xamarin.com/evolve/2013#session-0w86u7bco2



Related Topics



Leave a reply



Submit