Skip to content

Commit

Permalink
Make sure to unsubscribe from LayoutChanged on CV (#26138)
Browse files Browse the repository at this point in the history
* Make sure to unsubscribe from LayoutChanged on CV

* - cleanup
  • Loading branch information
PureWeen authored Nov 27, 2024
1 parent 1ce909f commit d91bf0a
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public abstract partial class ItemsViewHandler<TItemsView> : ViewHandler<TItemsV
protected override void DisconnectHandler(UIView platformView)
{
ItemsView.ScrollToRequested -= ScrollToRequested;
Controller?.DisposeItemsSource();
Controller?.Disconnect();
base.DisconnectHandler(platformView);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public void UpdateLayout(ItemsViewLayout newLayout)
}
}

internal virtual void Disconnect()
{
DisposeItemsSource();
}

protected override void Dispose(bool disposing)
{
if (_disposed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,37 @@ public StructuredItemsViewController(TItemsView structuredItemsView, ItemsViewLa
{
}


internal override void Disconnect()
{
base.Disconnect();

if (_headerViewFormsElement is not null)
{
_headerViewFormsElement.MeasureInvalidated -= OnFormsElementMeasureInvalidated;
}

if (_footerViewFormsElement is not null)
{
_footerViewFormsElement.MeasureInvalidated -= OnFormsElementMeasureInvalidated;
}

if (_headerUIView is MauiView hv)
{
hv.LayoutChanged -= HeaderViewLayoutChanged;
}

if (_footerUIView is MauiView fv)
{
fv.LayoutChanged -= FooterViewLayoutChanged;
}

_headerUIView = null;
_headerViewFormsElement = null;
_footerUIView = null;
_footerViewFormsElement = null;
}

protected override void Dispose(bool disposing)
{
if (_disposed)
Expand All @@ -36,30 +67,7 @@ protected override void Dispose(bool disposing)

if (disposing)
{
if (_headerViewFormsElement != null)
{
_headerViewFormsElement.MeasureInvalidated -= OnFormsElementMeasureInvalidated;
}

if (_footerViewFormsElement != null)
{
_footerViewFormsElement.MeasureInvalidated -= OnFormsElementMeasureInvalidated;
}

if (_headerUIView is MauiView hv)
{
hv.LayoutChanged -= HeaderViewLayoutChanged;
}

if (_footerUIView is MauiView fv)
{
fv.LayoutChanged -= FooterViewLayoutChanged;
}

_headerUIView = null;
_headerViewFormsElement = null;
_footerUIView = null;
_footerViewFormsElement = null;
Disconnect();
}

base.Dispose(disposing);
Expand Down Expand Up @@ -112,6 +120,11 @@ public override void ViewWillLayoutSubviews()

internal void UpdateFooterView()
{
if (_footerUIView is MauiView mvPrevious)
{
mvPrevious.LayoutChanged -= FooterViewLayoutChanged;
}

UpdateSubview(ItemsView?.Footer, ItemsView?.FooterTemplate, FooterTag,
ref _footerUIView, ref _footerViewFormsElement);
UpdateHeaderFooterPosition();
Expand All @@ -124,6 +137,11 @@ internal void UpdateFooterView()

internal void UpdateHeaderView()
{
if (_headerUIView is MauiView mvPrevious)
{
mvPrevious.LayoutChanged -= HeaderViewLayoutChanged;
}

UpdateSubview(ItemsView?.Header, ItemsView?.HeaderTemplate, HeaderTag,
ref _headerUIView, ref _headerViewFormsElement);
UpdateHeaderFooterPosition();
Expand Down
55 changes: 55 additions & 0 deletions src/Controls/tests/DeviceTests/Memory/MemoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,61 @@ await InvokeOnMainThreadAsync(async () =>
await AssertionExtensions.WaitForGC(viewReference, handlerReference, platformViewReference);
}

[Fact("CollectionView Header/Footer Doesn't Leak")]
public async Task CollectionViewHeaderFooterDoesntLeak()
{
SetupBuilder();

WeakReference viewReference = null;
WeakReference handlerReference = null;
WeakReference controllerReference = null;

var observable = new ObservableCollection<int> { 1 };
var navPage = new NavigationPage(new ContentPage { Title = "Page 1" });

await CreateHandlerAndAddToWindow(new Window(navPage), async () =>
{
var cv = new CollectionView
{
Footer = new VerticalStackLayout(),
Header = new VerticalStackLayout(),
ItemTemplate = new DataTemplate(() =>
{
var view = new Label
{
};
view.SetBinding(Label.TextProperty, ".");
return view;
}),
ItemsSource = observable
};
viewReference = new WeakReference(cv);
handlerReference = new WeakReference(cv.Handler);
await navPage.Navigation.PushAsync(new ContentPage
{
Content = cv
});
#if IOS
var controller = (cv.Handler as CollectionViewHandler).Controller;
controllerReference = new WeakReference(controller);
controller = null;
#else
controllerReference = new WeakReference(new object());
#endif
cv = null;
await navPage.Navigation.PopAsync();
});

await AssertionExtensions.WaitForGC(viewReference, handlerReference, controllerReference);
}

[Theory("Gesture Does Not Leak")]
[InlineData(typeof(DragGestureRecognizer))]
[InlineData(typeof(DropGestureRecognizer))]
Expand Down

0 comments on commit d91bf0a

Please sign in to comment.