-
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] fix memory leaks in GestureRecognizers
#21089
Conversation
Context: dotnet#20023 (comment) One of the underlying reasons `RadioButton` appears to leak, is that it happens to use a `TapGestureRecognizer` internally. When testing, @tj-devel709 and I found we could add pretty much *any* recognizer to a simple control like `BoxView`, and it would cause the `BoxView` to live forever. I could reproduce this in a new unit test that I setup for each type of gesture recognizer. `GesturePlatformManager.iOS.cs` had two "cycles": * `*Handler` -> ... -> `GesturePlatformManager` -> `*Handler` * `*GestureRecognizer` -> `GesturePlatformManager` -> `*GestureRecognizer` To fix these, we can make `GesturePlatformManager` not have strong references pointing *back* to its parent classes: * Make `_handler` a `WeakReference<IPlatformViewHandler>` * Make `_gestureRecognizers` a `ConditionalWeakTable` With these changes the tests pass, except for `PanGestureRecognizer`, which had a "closure" in using the `panRecognizer` variable. I fixed this one with: * `((PanGestureRecognizer)panGestureRecognizer)` as `panGestureRecognizer` was backed by a `WeakReference`.
This one we probably need to go manually test all the recognizers. Is there a sample I can try that? |
I don't think so, Device Tests don't really support gestures so we haven't really used them very heavily other than basic logical tests. |
Ok, it turns out in practice that Tested with dotnet/maui/main: Something like this works in the sample:
I will re-think this one. There is still an unsolved issue with
Note there is no |
Context: https://github.com/heyThorsten/GCTest Fixes: dotnet#20023 In testing the above sample, a page with a `RadioButton` has a memory leak due to the usage of `Border.StrokeShape`. There was also a slight "rabbit" hole, where we thought there was also an issue with `GestureRecognizers`. But this was not the case in a real app (just unit tests): * dotnet#21089 It turns out that when `GestureRecognizers` are used in `MemoryTests`, they will leak if there is no `Window` involved. Instead of using `MemoryTests` like I would traditionally, we should instead use `NavigationPageTests.DoesNotLeak()`. I can reproduce the problem with `RadioButton` *and* a `Window` exists in the test. ~~ Underlying issue ~~ It appears that `Border.StrokeShape` does not use the same pattern as other properties like `Border.Stroke`: * On set: `SetInheritedBindingContext(visualElement, BindingContext);` * On unset: `SetInheritedBindingContext(visualElement, null);` It instead used: * On set: `AddLogicalChild(visualElement);` * On unset: `RemoveLogicalChild(visualElement);` 6136a8a that introduced these does not mention why one was used over another. I am unsure if this will cause a problem, but it fixes the leak.
Context: https://github.com/heyThorsten/GCTest Fixes: #20023 In testing the above sample, a page with a `RadioButton` has a memory leak due to the usage of `Border.StrokeShape`. There was also a slight "rabbit" hole, where we thought there was also an issue with `GestureRecognizers`. But this was not the case in a real app (just unit tests): * #21089 It turns out that when `GestureRecognizers` are used in `MemoryTests`, they will leak if there is no `Window` involved. Instead of using `MemoryTests` like I would traditionally, we should instead use `NavigationPageTests.DoesNotLeak()`. I can reproduce the problem with `RadioButton` *and* a `Window` exists in the test. ~~ Underlying issue ~~ It appears that `Border.StrokeShape` does not use the same pattern as other properties like `Border.Stroke`: * On set: `SetInheritedBindingContext(visualElement, BindingContext);` * On unset: `SetInheritedBindingContext(visualElement, null);` It instead used: * On set: `AddLogicalChild(visualElement);` * On unset: `RemoveLogicalChild(visualElement);` 6136a8a that introduced these does not mention why one was used over another. I am unsure if this will cause a problem, but it fixes the leak. Other changes: * `propertyChanging` seems wrong? Reading the existing code, it should be checking `oldvalue` instead of `newvalue`?
Context: #20023 (comment)
One of the underlying reasons
RadioButton
appears to leak, is that it happens to use aTapGestureRecognizer
internally. When testing, @tj-devel709 and I found we could add pretty much any recognizer to a simple control likeBoxView
, and it would cause theBoxView
to live forever.I could reproduce this in a new unit test that I setup for each type of gesture recognizer.
GesturePlatformManager.iOS.cs
had two "cycles":*Handler
-> ... ->GesturePlatformManager
->*Handler
*GestureRecognizer
->GesturePlatformManager
->*GestureRecognizer
To fix these, we can make
GesturePlatformManager
not have strong references pointing back to its parent classes:Make
_handler
aWeakReference<IPlatformViewHandler>
Make
_gestureRecognizers
aConditionalWeakTable
With these changes the tests pass, except for
PanGestureRecognizer
, which had a "closure" in using thepanRecognizer
variable.I fixed this one with:
((PanGestureRecognizer)panGestureRecognizer)
aspanGestureRecognizer
was backed by aWeakReference
.