summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla32206.cs142
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla43941.cs136
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems2
-rw-r--r--Xamarin.Forms.Platform.iOS/Cells/CellTableViewCell.cs18
-rw-r--r--Xamarin.Forms.Platform.iOS/Cells/ViewCellRenderer.cs16
-rw-r--r--Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs149
6 files changed, 414 insertions, 49 deletions
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla32206.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla32206.cs
new file mode 100644
index 00000000..428538f6
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla32206.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using System.Threading;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Bugzilla, 32206, "ContextActions cause memory leak: Page is never destroyed", PlatformAffected.iOS)]
+ public class Bugzilla32206 : TestNavigationPage
+ {
+ protected override void Init()
+ {
+ PushAsync(new LandingPage32206());
+ }
+
+#if UITEST && __IOS__
+ [Test]
+ public void Bugzilla32206Test()
+ {
+ for (var n = 0; n < 10; n++)
+ {
+ RunningApp.WaitForElement(q => q.Marked("Push"));
+ RunningApp.Tap(q => q.Marked("Push"));
+
+ RunningApp.WaitForElement(q => q.Marked("ListView"));
+ RunningApp.Back();
+ }
+
+ // At this point, the counter can be any value, but it's most likely not zero.
+ // Invoking GC once is enough to clean up all garbage data and set counter to zero
+ RunningApp.WaitForElement(q => q.Marked("GC"));
+ RunningApp.Tap(q => q.Marked("GC"));
+
+ RunningApp.WaitForElement(q => q.Marked("Counter: 0"));
+ }
+#endif
+ }
+
+ [Preserve(AllMembers = true)]
+ public class LandingPage32206 : ContentPage
+ {
+ public static int Counter;
+ public Label Label;
+
+ public LandingPage32206()
+ {
+ Label = new Label
+ {
+ Text = "Counter: " + Counter,
+ HorizontalTextAlignment = TextAlignment.Center,
+ VerticalTextAlignment = TextAlignment.Center
+ };
+
+ Content = new StackLayout
+ {
+ Orientation = StackOrientation.Vertical,
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.Center,
+ Spacing = 15,
+ Children =
+ {
+ new Label
+ {
+ Text = "Click Push to show a ListView. When you hit the Back button, Counter will show the number of pages that have not been finalized yet."
+ + " If you click GC, the counter should be 0."
+ },
+ Label,
+ new Button
+ {
+ Text = "GC",
+ AutomationId = "GC",
+ Command = new Command(o =>
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ Label.Text = "Counter: " + Counter;
+ })
+ },
+ new Button
+ {
+ Text = "Push",
+ AutomationId = "Push",
+ Command = new Command(async o =>
+ {
+ await Navigation.PushAsync(new ContentPage32206());
+ })
+ }
+ }
+ };
+ }
+
+ protected override void OnAppearing()
+ {
+ base.OnAppearing();
+
+ if (Label != null)
+ Label.Text = "Counter: " + Counter;
+ }
+ }
+
+ [Preserve(AllMembers = true)]
+ public class ContentPage32206 : ContentPage
+ {
+ public ContentPage32206()
+ {
+ Interlocked.Increment(ref LandingPage32206.Counter);
+ System.Diagnostics.Debug.WriteLine("Page: " + LandingPage32206.Counter);
+
+ Content = new ListView
+ {
+ ItemsSource = new List<string> { "Apple", "Banana", "Cherry" },
+ ItemTemplate = new DataTemplate(typeof(ViewCell32206)),
+ AutomationId = "ListView"
+ };
+ }
+
+ ~ContentPage32206()
+ {
+ Interlocked.Decrement(ref LandingPage32206.Counter);
+ System.Diagnostics.Debug.WriteLine("Page: " + LandingPage32206.Counter);
+ }
+ }
+
+ [Preserve(AllMembers = true)]
+ public class ViewCell32206 : ViewCell
+ {
+ public ViewCell32206()
+ {
+ View = new Label();
+ View.SetBinding(Label.TextProperty, ".");
+ ContextActions.Add(new MenuItem { Text = "Delete" });
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla43941.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla43941.cs
new file mode 100644
index 00000000..b69e968a
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla43941.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using System.Threading;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Bugzilla, 43941, "Memory leak with ListView's RecycleElement on iOS", PlatformAffected.iOS)]
+ public class Bugzilla43941 : TestNavigationPage
+ {
+ protected override void Init()
+ {
+ PushAsync(new LandingPage43941());
+ }
+
+#if UITEST && __IOS__
+ [Test]
+ public void Bugzilla43941Test()
+ {
+ for (var n = 0; n < 10; n++)
+ {
+ RunningApp.WaitForElement(q => q.Marked("Push"));
+ RunningApp.Tap(q => q.Marked("Push"));
+
+ RunningApp.WaitForElement(q => q.Marked("ListView"));
+ RunningApp.Back();
+ }
+
+ // At this point, the counter can be any value, but it's most likely not zero.
+ // Invoking GC once is enough to clean up all garbage data and set counter to zero
+ RunningApp.WaitForElement(q => q.Marked("GC"));
+ RunningApp.Tap(q => q.Marked("GC"));
+
+ RunningApp.WaitForElement(q => q.Marked("Counter: 0"));
+ }
+#endif
+ }
+
+ [Preserve(AllMembers = true)]
+ public class ContentPage43941 : ContentPage
+ {
+ public ContentPage43941()
+ {
+ Interlocked.Increment(ref LandingPage43941.Counter);
+ System.Diagnostics.Debug.WriteLine("Page: " + LandingPage43941.Counter);
+
+ var list = new List<int>();
+ for (var i = 0; i < 30; i++)
+ list.Add(i);
+
+ Title = "ContentPage43941";
+ Content = new ListView
+ {
+ HasUnevenRows = true,
+ ItemsSource = list,
+ AutomationId = "ListView"
+ };
+ }
+
+ ~ContentPage43941()
+ {
+ Interlocked.Decrement(ref LandingPage43941.Counter);
+ System.Diagnostics.Debug.WriteLine("Page: " + LandingPage43941.Counter);
+ }
+ }
+
+ [Preserve(AllMembers = true)]
+ public class LandingPage43941 : ContentPage
+ {
+ public static int Counter;
+ public Label Label;
+
+ public LandingPage43941()
+ {
+ Label = new Label
+ {
+ Text = "Counter: " + Counter,
+ HorizontalTextAlignment = TextAlignment.Center,
+ VerticalTextAlignment = TextAlignment.Center
+ };
+
+ Content = new StackLayout
+ {
+ Orientation = StackOrientation.Vertical,
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.Center,
+ Spacing = 15,
+ Children =
+ {
+ new Label
+ {
+ Text = "Click Push to show a ListView. When you hit the Back button, Counter will show the number of pages that have not been finalized yet."
+ + " If you click GC, the counter should be 0."
+ },
+ Label,
+ new Button
+ {
+ Text = "GC",
+ AutomationId = "GC",
+ Command = new Command(o =>
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ Label.Text = "Counter: " + Counter;
+ })
+ },
+ new Button
+ {
+ Text = "Push",
+ AutomationId = "Push",
+ Command = new Command(async o =>
+ {
+ await Navigation.PushAsync(new ContentPage43941());
+ })
+ }
+ }
+ };
+ }
+
+ protected override void OnAppearing()
+ {
+ base.OnAppearing();
+
+ if (Label != null)
+ Label.Text = "Counter: " + Counter;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
index c4594010..23bb7879 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
@@ -51,6 +51,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla31964.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla32033.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla32034.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Bugzilla32206.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla32776.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla32842.xaml.cs">
<DependentUpon>Bugzilla32842.xaml</DependentUpon>
@@ -139,6 +140,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla43313.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla43469.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla43516.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Bugzilla43941.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla43663.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla43735.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla44453.cs" />
diff --git a/Xamarin.Forms.Platform.iOS/Cells/CellTableViewCell.cs b/Xamarin.Forms.Platform.iOS/Cells/CellTableViewCell.cs
index e32e86cd..b42f69f4 100644
--- a/Xamarin.Forms.Platform.iOS/Cells/CellTableViewCell.cs
+++ b/Xamarin.Forms.Platform.iOS/Cells/CellTableViewCell.cs
@@ -7,8 +7,8 @@ namespace Xamarin.Forms.Platform.iOS
public class CellTableViewCell : UITableViewCell, INativeElementView
{
Cell _cell;
-
public Action<object, PropertyChangedEventArgs> PropertyChanged;
+ bool _disposed;
public CellTableViewCell(UITableViewCellStyle style, string key) : base(style, key)
{
@@ -95,5 +95,21 @@ namespace Xamarin.Forms.Platform.iOS
return nativeCell;
}
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ if (disposing)
+ {
+ PropertyChanged = null;
+ _cell = null;
+ }
+
+ _disposed = true;
+
+ base.Dispose(disposing);
+ }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Cells/ViewCellRenderer.cs b/Xamarin.Forms.Platform.iOS/Cells/ViewCellRenderer.cs
index 8f8a92ce..30c17234 100644
--- a/Xamarin.Forms.Platform.iOS/Cells/ViewCellRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Cells/ViewCellRenderer.cs
@@ -48,9 +48,12 @@ namespace Xamarin.Forms.Platform.iOS
internal class ViewTableCell : UITableViewCell, INativeElementView
{
WeakReference<IVisualElementRenderer> _rendererRef;
-
ViewCell _viewCell;
+ Element INativeElementView.Element => ViewCell;
+ internal bool SupressSeparator { get; set; }
+ bool _disposed;
+
public ViewTableCell(string key) : base(UITableViewCellStyle.Default, key)
{
}
@@ -66,10 +69,6 @@ namespace Xamarin.Forms.Platform.iOS
}
}
- Element INativeElementView.Element => ViewCell;
-
- internal bool SupressSeparator { get; set; }
-
public override void LayoutSubviews()
{
//This sets the content views frame.
@@ -115,6 +114,9 @@ namespace Xamarin.Forms.Platform.iOS
protected override void Dispose(bool disposing)
{
+ if (_disposed)
+ return;
+
if (disposing)
{
IVisualElementRenderer renderer;
@@ -126,8 +128,12 @@ namespace Xamarin.Forms.Platform.iOS
_rendererRef = null;
}
+
+ _viewCell = null;
}
+ _disposed = true;
+
base.Dispose(disposing);
}
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs
index 3c235806..fd0f15f6 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ListViewRenderer.cs
@@ -29,6 +29,7 @@ namespace Xamarin.Forms.Platform.iOS
IListViewController Controller => Element;
ITemplatedItemsView<Cell> TemplatedItemsView => Element;
public override UIViewController ViewController => _tableViewController;
+ bool _disposed;
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
@@ -90,28 +91,31 @@ namespace Xamarin.Forms.Platform.iOS
}
}
+ void DisposeSubviews(UIView view)
+ {
+ foreach (UIView subView in view.Subviews)
+ DisposeSubviews(subView);
+
+ view.RemoveFromSuperview();
+ view.Dispose();
+ }
+
protected override void Dispose(bool disposing)
{
- // check inset tracker for null to
- if (disposing && _insetTracker != null)
- {
- _insetTracker.Dispose();
- _insetTracker = null;
+ if (_disposed)
+ return;
- var viewsToLookAt = new Stack<UIView>(Subviews);
- while (viewsToLookAt.Count > 0)
+ if (disposing)
+ {
+ if (_insetTracker != null)
{
- var view = viewsToLookAt.Pop();
- var viewCellRenderer = view as ViewCellRenderer.ViewTableCell;
- if (viewCellRenderer != null)
- viewCellRenderer.Dispose();
- else
- {
- foreach (var child in view.Subviews)
- viewsToLookAt.Push(child);
- }
+ _insetTracker.Dispose();
+ _insetTracker = null;
}
+ foreach (UIView subview in Subviews)
+ DisposeSubviews(subview);
+
if (Element != null)
{
var templatedItems = TemplatedItemsView.TemplatedItems;
@@ -119,27 +123,28 @@ namespace Xamarin.Forms.Platform.iOS
templatedItems.GroupedCollectionChanged -= OnGroupedCollectionChanged;
}
+ if (_dataSource != null)
+ {
+ _dataSource.Dispose();
+ _dataSource = null;
+ }
+
if (_tableViewController != null)
{
_tableViewController.Dispose();
_tableViewController = null;
}
- }
- if (disposing)
- {
if (_headerRenderer != null)
{
- var platform = _headerRenderer.Element.Platform as Platform;
- if (platform != null)
- platform.DisposeModelAndChildrenRenderers(_headerRenderer.Element);
+ var platform = _headerRenderer.Element?.Platform as Platform;
+ platform?.DisposeModelAndChildrenRenderers(_headerRenderer.Element);
_headerRenderer = null;
}
if (_footerRenderer != null)
{
- var platform = _footerRenderer.Element.Platform as Platform;
- if (platform != null)
- platform.DisposeModelAndChildrenRenderers(_footerRenderer.Element);
+ var platform = _footerRenderer.Element?.Platform as Platform;
+ platform?.DisposeModelAndChildrenRenderers(_footerRenderer.Element);
_footerRenderer = null;
}
@@ -154,6 +159,8 @@ namespace Xamarin.Forms.Platform.iOS
Control?.TableFooterView?.Dispose();
}
+ _disposed = true;
+
base.Dispose(disposing);
}
@@ -602,6 +609,7 @@ namespace Xamarin.Forms.Platform.iOS
internal class UnevenListViewDataSource : ListViewDataSource
{
IVisualElementRenderer _prototype;
+ bool _disposed;
public UnevenListViewDataSource(ListView list, FormsUITableViewController uiTableViewController) : base(list, uiTableViewController)
{
@@ -661,27 +669,47 @@ namespace Xamarin.Forms.Platform.iOS
{
var target = viewCell.View;
if (_prototype == null)
- {
_prototype = Platform.CreateRenderer(target);
- Platform.SetRenderer(target, _prototype);
- }
else
- {
_prototype.SetElement(target);
- Platform.SetRenderer(target, _prototype);
- }
+
+ Platform.SetRenderer(target, _prototype);
var req = target.Measure(tableView.Frame.Width, double.PositiveInfinity, MeasureFlags.IncludeMargins);
target.ClearValue(Platform.RendererProperty);
- foreach (var descendant in target.Descendants())
+ foreach (Element descendant in target.Descendants())
+ {
+ IVisualElementRenderer renderer = Platform.GetRenderer(descendant as VisualElement);
descendant.ClearValue(Platform.RendererProperty);
+ renderer?.Dispose();
+ }
return (nfloat)req.Request.Height;
}
+
var renderHeight = cell.RenderHeight;
return renderHeight > 0 ? (nfloat)renderHeight : DefaultRowHeight;
}
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+
+ if (disposing)
+ {
+ if (_prototype != null)
+ {
+ _prototype.Dispose();
+ _prototype = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
}
internal class ListViewDataSource : UITableViewSource
@@ -689,14 +717,15 @@ namespace Xamarin.Forms.Platform.iOS
const int DefaultItemTemplateId = 1;
static int s_dataTemplateIncrementer = 2; // lets start at not 0 because
readonly nfloat _defaultSectionHeight;
- readonly Dictionary<DataTemplate, int> _templateToId = new Dictionary<DataTemplate, int>();
- readonly UITableView _uiTableView;
- readonly FormsUITableViewController _uiTableViewController;
- protected readonly ListView List;
+ Dictionary<DataTemplate, int> _templateToId = new Dictionary<DataTemplate, int>();
+ UITableView _uiTableView;
+ FormsUITableViewController _uiTableViewController;
+ protected ListView List;
IListViewController Controller => List;
protected ITemplatedItemsView<Cell> TemplatedItemsView => List;
bool _isDragging;
bool _selectionFromNative;
+ bool _disposed;
public ListViewDataSource(ListViewDataSource source)
{
@@ -1016,6 +1045,29 @@ namespace Xamarin.Forms.Platform.iOS
((INotifyCollectionChanged)templatedList.ShortNames).CollectionChanged -= OnShortNamesCollectionChanged;
}
}
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ if (disposing)
+ {
+ if (List != null)
+ {
+ List.ItemSelected -= OnItemSelected;
+ List = null;
+ }
+
+ _templateToId = null;
+ _uiTableView = null;
+ _uiTableViewController = null;
+ }
+
+ _disposed = true;
+
+ base.Dispose(disposing);
+ }
}
}
@@ -1031,11 +1083,12 @@ namespace Xamarin.Forms.Platform.iOS
internal class FormsUITableViewController : UITableViewController
{
- readonly ListView _list;
+ ListView _list;
IListViewController Controller => _list;
UIRefreshControl _refresh;
bool _refreshAdded;
+ bool _disposed;
public FormsUITableViewController(ListView element)
{
@@ -1117,15 +1170,25 @@ namespace Xamarin.Forms.Platform.iOS
protected override void Dispose(bool disposing)
{
- base.Dispose(disposing);
+ if (_disposed)
+ return;
- if (disposing && _refresh != null)
+ if (disposing)
{
- _refresh.ValueChanged -= OnRefreshingChanged;
- _refresh.EndRefreshing();
- _refresh.Dispose();
- _refresh = null;
+ if (_refresh != null)
+ {
+ _refresh.ValueChanged -= OnRefreshingChanged;
+ _refresh.EndRefreshing();
+ _refresh.Dispose();
+ _refresh = null;
+ }
+
+ _list = null;
}
+
+ _disposed = true;
+
+ base.Dispose(disposing);
}
void CheckContentSize()