-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[iOS] Clear BindingContext when cell is queued for reuse #14619
Conversation
Thank you for your pull request. We are auto-formatting your source code to follow our code guidelines. |
/azp run |
Azure Pipelines successfully started running 2 pipeline(s). |
|
The repo uses https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md. The APIs are listed in files like https://github.com/dotnet/maui/blob/main/src/Core/src/PublicAPI/net-ios/PublicAPI.Shipped.txt. It seems that Visual Studio might help you with a lightbulb suggestion to modify text files for you. No guarrantees. Just a passerby. |
Aah yeah, I should make a doc for this at some point I guess... What @MartyIX says is pretty much it! It should give you red squiggles, let IntelliSense fix it. Let me know if you can't figure it out! |
I did figure it out but apparently there's something broken on GitHub since it doesn't show the new commit in the PR (despite being visible on the branch if I click through). |
/azp run |
Azure Pipelines successfully started running 2 pipeline(s). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need the public api changes also for net7.0-maccatalyst
Done. |
/azp run |
Azure Pipelines successfully started running 2 pipeline(s). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to unit test this? @jonathanpeppers you are now the expert :)
Not sure if iOS will call the methods, but maybe with a collection view that has some items, then clear all items and see if the binding context is collected? Is there maybe some example test that can be copied?
I think we need a device test for
Maybe someone on the MAUI team can help write this test? I don't know how to do step 2. Here is an example with maui/src/Controls/tests/DeviceTests/Elements/NavigationPage/NavigationPageTests.cs Lines 295 to 296 in 2b823f8
|
There should be a way to observe it like this:
That doesn't necessarily ensure that all references to the items are gone but it's observable that this specific one is cleared. (Unfortunately I left my laptop at office so I cannot look into writing it now...) |
@filipnavara I found this test: maui/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.cs Lines 37 to 38 in 2b823f8
It asserts that after a given Does it prove there might not be a problem here? Or do we need to adjust this test? It might not reproduce the problem exactly. |
This passes on Windows with dotnet/maui/main, but I wonder if it fails on iOS/Catalyst: [Fact]
public async Task ClearingItemsSourceDoesNotLeak()
{
SetupBuilder();
IList logicalChildren = null;
WeakReference weakReference = null;
var items = new List<WeakReference>();
var collectionView = new CollectionView
{
ItemTemplate = new DataTemplate(() => new Label())
};
await CreateHandlerAndAddToWindow<CollectionViewHandler>(collectionView, async handler =>
{
var data = new ObservableCollection<MyRecord>()
{
new MyRecord("Item 1"),
new MyRecord("Item 2"),
new MyRecord("Item 3"),
};
foreach (var item in data)
items.Add(new WeakReference(item));
weakReference = new WeakReference(data);
collectionView.ItemsSource = data;
await Task.Delay(100);
// Get ItemsView._logicalChildren
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
logicalChildren = typeof(ItemsView).GetField("_logicalChildren", flags).GetValue(collectionView) as IList;
Assert.NotNull(logicalChildren);
// Clear collection
collectionView.ItemsSource = null;
await Task.Delay(100);
});
await Task.Yield();
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.NotNull(weakReference);
Assert.False(weakReference.IsAlive, "ObservableCollection should not be alive!");
Assert.NotNull(logicalChildren);
Assert.True(logicalChildren.Count <= 3, "_logicalChildren should not grow in size!");
foreach (var item in items)
{
Assert.False(weakReference.IsAlive, "MyRecord should not be alive!");
}
}
record MyRecord(string Name); |
Good find. I'll have to figure out how to debug the iOS tests in my environment. Few observations by just reading it though:
|
I managed to run the test. I am not entirely sure what is going on yet, but the |
I modified the test to actually match my comment above only to discover that it doesn't actually quite solve the issue described in the PR's first comment. I had to apply few explicit width/height requests to get everything to display but that was relatively easy. Now I have to take a step back to explain why I pursued this in the first place and why it helps my case but doesn't quite fix the leak. We use a custom collection that is backed by a database and in-memory cache and we often bind an item source with 10k-100k items into the collection view. There are some quirks about how this is handled. One of them is that some objects could be reloaded from database and no longer satisfy reference equality (to be resolved by #14613) while still satisfying We had the (There are probably cases where (*) The independent I found a partial explanation of what is happening under the covers (https://openradar.appspot.com/39604024):
There's also a vague note in the official documentation:
I will check what happens on older iOS versions and/or if prefetching is disabled. |
I found a way to accomplish the correct behaviour with The options I examined:
|
Azure Pipelines successfully started running 3 pipeline(s). |
This avoid holding references to objects that were already removed.
…t to list PrepareForReuse override.
…item is no longer present in items source. Extend the test to check rebinding after clearing the source.
Co-authored-by: Rui Marinho <me@ruimarinho.net>
/azp run |
Azure Pipelines successfully started running 3 pipeline(s). |
/rebase |
Description of Change
This avoid holding references to objects that were already removed. Previously
UICollectionView
would cache the cells for reuse and hold the references to bound objects for undefined period of time.