Skip to content

Commit 2007ab7

Browse files
[ios] fix memory leak in RadioButton (#21151)
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`?
1 parent 8569185 commit 2007ab7

File tree

2 files changed

+22
-10
lines changed

2 files changed

+22
-10
lines changed

src/Controls/src/Core/Border/Border.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public Thickness Padding
4949
BindableProperty.Create(nameof(StrokeShape), typeof(IShape), typeof(Border), new Rectangle(),
5050
propertyChanging: (bindable, oldvalue, newvalue) =>
5151
{
52-
if (newvalue is not null)
52+
if (oldvalue is not null)
5353
(bindable as Border)?.StopNotifyingStrokeShapeChanges();
5454
},
5555
propertyChanged: (bindable, oldvalue, newvalue) =>
@@ -64,10 +64,13 @@ void NotifyStrokeShapeChanges()
6464

6565
if (strokeShape is VisualElement visualElement)
6666
{
67-
AddLogicalChild(visualElement);
67+
SetInheritedBindingContext(visualElement, BindingContext);
6868
_strokeShapeChanged ??= (sender, e) => OnPropertyChanged(nameof(StrokeShape));
6969
_strokeShapeProxy ??= new();
7070
_strokeShapeProxy.Subscribe(visualElement, _strokeShapeChanged);
71+
72+
OnParentResourcesChanged(this.GetMergedResources());
73+
((IElementDefinition)this).AddResourcesChangedListener(visualElement.OnParentResourcesChanged);
7174
}
7275
}
7376

@@ -77,7 +80,9 @@ void StopNotifyingStrokeShapeChanges()
7780

7881
if (strokeShape is VisualElement visualElement)
7982
{
80-
RemoveLogicalChild(visualElement);
83+
((IElementDefinition)this).RemoveResourcesChangedListener(visualElement.OnParentResourcesChanged);
84+
85+
SetInheritedBindingContext(visualElement, null);
8186
_strokeShapeProxy?.Unsubscribe();
8287
}
8388
}
@@ -87,7 +92,7 @@ void StopNotifyingStrokeShapeChanges()
8792
BindableProperty.Create(nameof(Stroke), typeof(Brush), typeof(Border), null,
8893
propertyChanging: (bindable, oldvalue, newvalue) =>
8994
{
90-
if (newvalue is not null)
95+
if (oldvalue is not null)
9196
(bindable as Border)?.StopNotifyingStrokeChanges();
9297
},
9398
propertyChanged: (bindable, oldvalue, newvalue) =>

src/Controls/tests/DeviceTests/Elements/NavigationPage/NavigationPageTests.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
using System.Text;
55
using System.Threading.Tasks;
66
using Microsoft.Maui.Controls;
7+
using Microsoft.Maui.Controls.Handlers;
78
using Microsoft.Maui.Controls.Handlers.Compatibility;
89
using Microsoft.Maui.Controls.Handlers.Items;
10+
using Microsoft.Maui.Controls.Shapes;
911
using Microsoft.Maui.DeviceTests.Stubs;
1012
using Microsoft.Maui.Handlers;
1113
using Microsoft.Maui.Hosting;
@@ -33,15 +35,19 @@ void SetupBuilder()
3335
handlers.AddHandler(typeof(TabbedPage), typeof(TabbedViewHandler));
3436
#endif
3537
handlers.AddHandler(typeof(FlyoutPage), typeof(FlyoutViewHandler));
36-
handlers.AddHandler<Page, PageHandler>();
37-
handlers.AddHandler<Window, WindowHandlerStub>();
38-
handlers.AddHandler<Frame, FrameRenderer>();
39-
handlers.AddHandler<Label, LabelHandler>();
38+
handlers.AddHandler(typeof(ScrollView), typeof(ScrollViewHandler));
39+
handlers.AddHandler<Border, BorderHandler>();
4040
handlers.AddHandler<Button, ButtonHandler>();
4141
handlers.AddHandler<CarouselView, CarouselViewHandler>();
4242
handlers.AddHandler<CollectionView, CollectionViewHandler>();
43-
handlers.AddHandler(typeof(Controls.ContentView), typeof(ContentViewHandler));
44-
handlers.AddHandler(typeof(ScrollView), typeof(ScrollViewHandler));
43+
handlers.AddHandler<Frame, FrameRenderer>();
44+
handlers.AddHandler<IContentView, ContentViewHandler>();
45+
handlers.AddHandler<Label, LabelHandler>();
46+
handlers.AddHandler<Layout, LayoutHandler>();
47+
handlers.AddHandler<Page, PageHandler>();
48+
handlers.AddHandler<RadioButton, RadioButtonHandler>();
49+
handlers.AddHandler<Shape, ShapeViewHandler>();
50+
handlers.AddHandler<Window, WindowHandlerStub>();
4551
});
4652
});
4753
}
@@ -323,6 +329,7 @@ await CreateHandlerAndAddToWindow<WindowHandlerStub>(new Window(navPage), async
323329
new ContentView(),
324330
new Label(),
325331
new ScrollView(),
332+
new RadioButton(),
326333
}
327334
};
328335
pageReference = new WeakReference(page);

0 commit comments

Comments
 (0)