[ios] fix memory leaks in various controls (#18434)

Fixes:

* `ActivityIndicator`
* `BoxView`
* `Polygon`
* `Polyline`
* `IndicatorView`

Some of these were based on `PlatformGraphicsView`, which had a circular
reference, for example:

* `BoxView` -> `BoxViewHandler`/`PlatformGraphicsViewHandler` ->
  `PlatformGraphicsView` -> `BoxView`

* It appears both `IDrawable` and `IGraphicsRenderer` values in
  `PlatformGraphicsView` were circular references.

Various shapes were based on `PlatformGraphicsView`. We don't have
the iOS memory analyzer running on `Microsoft.Maui.Graphics.dll` yet.

Similarly:

* `ActivityIndicator` -> `ActivityIndicatorHandler` ->
  `MauiActivityIndicator` -> `ActivityIndicator`

* `IndicatorView` -> `IndicatorViewHandler` ->
  `MauiPageControl` -> `IndicatorView`

*Open question*: Why didn't the analyzer catch the issues with
`ActivityIndicator` and `IndicatorView`? Investigating why.
This commit is contained in:
Jonathan Peppers 2023-10-30 17:22:53 -05:00 коммит произвёл GitHub
Родитель 6fa96e9f02
Коммит a589b120ce
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 64 добавлений и 46 удалений

Просмотреть файл

@ -1,7 +1,9 @@
using System;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Handlers;
using Microsoft.Maui.Controls.Handlers.Compatibility;
using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Hosting;
using Xunit;
@ -17,7 +19,9 @@ public class MemoryTests : ControlsHandlerTestBase
{
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<ActivityIndicator, ActivityIndicatorHandler>();
handlers.AddHandler<Border, BorderHandler>();
handlers.AddHandler<BoxView, BoxViewHandler>();
handlers.AddHandler<CheckBox, CheckBoxHandler>();
handlers.AddHandler<DatePicker, DatePickerHandler>();
handlers.AddHandler<Entry, EntryHandler>();
@ -26,18 +30,24 @@ public class MemoryTests : ControlsHandlerTestBase
handlers.AddHandler<Label, LabelHandler>();
handlers.AddHandler<ListView, ListViewRenderer>();
handlers.AddHandler<Picker, PickerHandler>();
handlers.AddHandler<Polygon, PolygonHandler>();
handlers.AddHandler<Polyline, PolylineHandler>();
handlers.AddHandler<IContentView, ContentViewHandler>();
handlers.AddHandler<Image, ImageHandler>();
handlers.AddHandler<IndicatorView, IndicatorViewHandler>();
handlers.AddHandler<RefreshView, RefreshViewHandler>();
handlers.AddHandler<IScrollView, ScrollViewHandler>();
handlers.AddHandler<SwipeView, SwipeViewHandler>();
handlers.AddHandler<TimePicker, TimePickerHandler>();
handlers.AddHandler<WebView, WebViewHandler>();
});
});
}
[Theory("Handler Does Not Leak")]
[InlineData(typeof(ActivityIndicator))]
[InlineData(typeof(Border))]
[InlineData(typeof(BoxView))]
[InlineData(typeof(ContentView))]
[InlineData(typeof(CheckBox))]
[InlineData(typeof(DatePicker))]
@ -45,12 +55,16 @@ public class MemoryTests : ControlsHandlerTestBase
[InlineData(typeof(Editor))]
[InlineData(typeof(GraphicsView))]
[InlineData(typeof(Image))]
[InlineData(typeof(IndicatorView))]
[InlineData(typeof(Label))]
[InlineData(typeof(Picker))]
[InlineData(typeof(Polygon))]
[InlineData(typeof(Polyline))]
[InlineData(typeof(RefreshView))]
[InlineData(typeof(ScrollView))]
[InlineData(typeof(SwipeView))]
[InlineData(typeof(TimePicker))]
[InlineData(typeof(WebView))]
public async Task HandlerDoesNotLeak(Type type)
{
SetupBuilder();

Просмотреть файл

@ -8,18 +8,21 @@ namespace Microsoft.Maui.Platform
{
public class MauiActivityIndicator : UIActivityIndicatorView, IUIViewLifeCycleEvents
{
IActivityIndicator? _virtualView;
readonly WeakReference<IActivityIndicator>? _virtualView;
bool IsRunning => _virtualView is not null && _virtualView.TryGetTarget(out var a) ? a.IsRunning : false;
public MauiActivityIndicator(CGRect rect, IActivityIndicator? virtualView) : base(rect)
{
_virtualView = virtualView;
if (virtualView is not null)
_virtualView = new(virtualView);
}
public override void Draw(CGRect rect)
{
base.Draw(rect);
if (_virtualView?.IsRunning == true)
if (IsRunning)
StartAnimating();
else
StopAnimating();
@ -29,7 +32,7 @@ namespace Microsoft.Maui.Platform
{
base.LayoutSubviews();
if (_virtualView?.IsRunning == true)
if (IsRunning)
StartAnimating();
else
StopAnimating();
@ -38,10 +41,10 @@ namespace Microsoft.Maui.Platform
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_virtualView = null;
}
readonly WeakEventManager _weakEventManager = new();
[UnconditionalSuppressMessage("Memory", "MA0002", Justification = IUIViewLifeCycleEvents.UnconditionalSuppressMessage)]
EventHandler? _movedToWindow;
event EventHandler IUIViewLifeCycleEvents.MovedToWindow

Просмотреть файл

@ -1,6 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata;
using CoreGraphics;
using ObjCRuntime;
using UIKit;
@ -11,7 +10,7 @@ namespace Microsoft.Maui.Platform
{
const int DefaultIndicatorSize = 6;
IIndicatorView? _indicatorView;
WeakReference<IIndicatorView>? _indicatorView;
bool _updatingPosition;
public MauiPageControl()
@ -30,7 +29,7 @@ namespace Microsoft.Maui.Platform
{
ValueChanged -= MauiPageControlValueChanged;
}
_indicatorView = indicatorView;
_indicatorView = indicatorView is null ? null : new(indicatorView);
}
@ -81,11 +80,11 @@ namespace Microsoft.Maui.Platform
int GetCurrentPage()
{
if (_indicatorView == null)
if (_indicatorView is null || !_indicatorView.TryGetTarget(out var indicatorView))
return -1;
var maxVisible = _indicatorView.GetMaximumVisible();
var position = _indicatorView.Position;
var maxVisible = indicatorView.GetMaximumVisible();
var position = indicatorView.Position;
var index = position >= maxVisible ? maxVisible - 1 : position;
return index;
}
@ -93,9 +92,9 @@ namespace Microsoft.Maui.Platform
public void UpdateIndicatorCount()
{
if (_indicatorView == null)
if (_indicatorView is null || !_indicatorView.TryGetTarget(out var indicatorView))
return;
this.UpdatePages(_indicatorView.GetMaximumVisible());
this.UpdatePages(indicatorView.GetMaximumVisible());
UpdatePosition();
}
@ -136,10 +135,10 @@ namespace Microsoft.Maui.Platform
void MauiPageControlValueChanged(object? sender, System.EventArgs e)
{
if (_updatingPosition || _indicatorView == null)
if (_updatingPosition || _indicatorView is null || !_indicatorView.TryGetTarget(out var indicatorView))
return;
_indicatorView.Position = (int)CurrentPage;
indicatorView.Position = (int)CurrentPage;
//if we are iOS13 or lower and we are using a Square shape
//we need to update the CornerRadius of the new shape.
if (IsSquare && !(OperatingSystem.IsIOSVersionAtLeast(14) || OperatingSystem.IsTvOSVersionAtLeast(14)))

Просмотреть файл

@ -1,5 +1,4 @@
using System;
using System.Drawing;
using CoreGraphics;
using Foundation;
using UIKit;
@ -9,9 +8,9 @@ namespace Microsoft.Maui.Graphics.Platform
[Register(nameof(PlatformGraphicsView))]
public class PlatformGraphicsView : UIView
{
private IGraphicsRenderer _renderer;
private WeakReference<IGraphicsRenderer> _renderer;
private CGColorSpace _colorSpace;
private IDrawable _drawable;
private WeakReference<IDrawable> _drawable;
private CGRect _lastBounds;
public PlatformGraphicsView(CGRect frame, IDrawable drawable = null, IGraphicsRenderer renderer = null) : base(frame)
@ -35,57 +34,53 @@ namespace Microsoft.Maui.Graphics.Platform
public IGraphicsRenderer Renderer
{
get => _renderer;
get => _renderer is not null && _renderer.TryGetTarget(out var r) ? r : null;
set
{
if (_renderer != null)
var renderer = Renderer;
if (renderer != null)
{
_renderer.Drawable = null;
_renderer.GraphicsView = null;
_renderer.Dispose();
renderer.Drawable = null;
renderer.GraphicsView = null;
renderer.Dispose();
}
_renderer = value ?? new DirectRenderer();
renderer = value ?? new DirectRenderer();
_renderer = new(renderer);
_renderer.GraphicsView = this;
_renderer.Drawable = _drawable;
renderer.GraphicsView = this;
renderer.Drawable = Drawable;
var bounds = Bounds;
_renderer.SizeChanged((float)bounds.Width, (float)bounds.Height);
renderer.SizeChanged((float)bounds.Width, (float)bounds.Height);
}
}
public IDrawable Drawable
{
get => _drawable;
get => _drawable is not null && _drawable.TryGetTarget(out var d) ? d : null;
set
{
_drawable = value;
if (_renderer != null)
_drawable = new(value);
if (Renderer is IGraphicsRenderer renderer)
{
_renderer.Drawable = _drawable;
_renderer.Invalidate();
renderer.Drawable = value;
renderer.Invalidate();
}
}
}
public void InvalidateDrawable()
{
_renderer.Invalidate();
}
public void InvalidateDrawable() => Renderer?.Invalidate();
public void InvalidateDrawable(float x, float y, float w, float h)
{
_renderer.Invalidate(x, y, w, h);
}
public void InvalidateDrawable(float x, float y, float w, float h) => Renderer?.Invalidate(x, y, w, h);
public override void WillMoveToSuperview(UIView newSuperview)
{
base.WillMoveToSuperview(newSuperview);
if (newSuperview == null)
if (newSuperview == null && Renderer is IGraphicsRenderer renderer)
{
_renderer.Detached();
renderer.Detached();
}
}
@ -107,7 +102,10 @@ namespace Microsoft.Maui.Graphics.Platform
coreGraphics.SetStrokeColorSpace(_colorSpace);
coreGraphics.SetPatternPhase(PatternPhase);
_renderer.Draw(coreGraphics, dirtyRect.AsRectangleF());
if (Renderer is IGraphicsRenderer renderer)
{
renderer.Draw(coreGraphics, dirtyRect.AsRectangleF());
}
}
public override CGRect Bounds
@ -120,8 +118,12 @@ namespace Microsoft.Maui.Graphics.Platform
if (_lastBounds.Width != newBounds.Width || _lastBounds.Height != newBounds.Height)
{
base.Bounds = value;
_renderer.SizeChanged((float)newBounds.Width, (float)newBounds.Height);
_renderer.Invalidate();
if (Renderer is IGraphicsRenderer renderer)
{
renderer.SizeChanged((float)newBounds.Width, (float)newBounds.Height);
renderer.Invalidate();
}
_lastBounds = newBounds;
}