-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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] fix memory leak in TimePicker #16265
Conversation
This is an alternate idea to: dotnet#16265
We are discussing if there could be a handler lifecycle change to make this easier. For now, I might rework this change to be smaller and only fix |
827be04
to
5c9ffd3
Compare
5c9ffd3
to
706ccdd
Compare
[InlineData(typeof(TimePicker))] | ||
public async Task HandlerDoesNotLeak(Type type) |
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.
I reworked this PR to use the new test pattern in 0e27ca4.
The test was failing without these changes, so it makes these PRs easier.
public partial class TimePickerHandler : ViewHandler<ITimePicker, UIDatePicker> | ||
{ | ||
readonly UIDatePickerProxy _proxy = new(); |
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.
I found MacCatalyst has a different handler than iOS, so I found I had to make fixes in here as well.
706ccdd
to
9079f91
Compare
After working on some tooling to proactively find memory leaks, I found a few in the `TimePicker`. This kind of highlights the pattern: * `MauiTimePicker` is a subclass of `UIDatePicker` * `TimePickerHandler` has a strong reference to `MauiTimePicker` as the `PlatformView`. * `TimePickerHandler` subscribes to various events and has a strong reference to `MauiTimePicker`: creating a cycle. This effectively causes entire pages to leak, because `TimePicker.Parent.Parent`, etc. will hold a strong reference up to the entire `Page`. It is not enough to unsubscribe from these events in `DisconnectHandler`, because it is not called in many cases. For example, take the test: await InvokeOnMainThreadAsync(() => { var layout = new Grid(); var picker = new TimePicker(); layout.Add(picker); var handler = CreateHandler<LayoutHandler>(layout); viewReference = new WeakReference(handler); handlerReference = new WeakReference(picker.Handler); platformViewReference = new WeakReference(picker.Handler.PlatformView); }); await AssertionExtensions.WaitForGC(viewReference, handlerReference, platformViewReference); Assert.False(viewReference.IsAlive, "TimePicker should not be alive!"); Assert.False(handlerReference.IsAlive, "Handler should not be alive!"); Assert.False(platformViewReference.IsAlive, "PlatformView should not be alive!"); In this case, `Grid`'s `LayoutHandler.DisconnectHandler` is called, but not the `TimePickerHandler`. To solve this problem, I moved 5 events to the `MauiTimePicker` class and was able to remove one. I also found a similar problem with `MauiDoneAccessoryView` and converted various `Action` callbacks to use `WeakReference` instead. The test did not pass until solving all of the above.
After working on some tooling to proactively find memory leaks, I found a few in the
TimePicker
. This kind of highlights the pattern:MauiTimePicker
is a subclass ofUIDatePicker
TimePickerHandler
has a strong reference toMauiTimePicker
as thePlatformView
.TimePickerHandler
subscribes to various events and has a strong reference toMauiTimePicker
: creating a cycle.This effectively causes entire pages to leak, because
TimePicker.Parent.Parent
, etc. will hold a strong reference up to the entirePage
.It is not enough to unsubscribe from these events in
DisconnectHandler
, because it is not called in many cases.For example, take the test:
In this case,
Grid
'sLayoutHandler.DisconnectHandler
is called, but not theTimePickerHandler
.To solve this problem, I moved 5 events to the
MauiTimePicker
class and was able to remove one.I also found a similar problem with
MauiDoneAccessoryView
and converted variousAction
callbacks to useWeakReference
instead.The test did not pass until solving all of the above.