Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O baixo desempenho do aplicativo se apresenta de várias maneiras. Ele pode fazer um aplicativo parecer sem resposta, pode causar rolagem lenta e pode reduzir a duração da bateria do dispositivo. No entanto, otimizar o desempenho envolve mais do que apenas implementar um código eficiente. A experiência do usuário com o desempenho do aplicativo também deve ser considerada. Por exemplo, garantir que as operações sejam executadas sem impedir que o usuário execute outras atividades pode ajudar a melhorar a experiência do usuário.
Há muitas técnicas para aumentar o desempenho e o desempenho percebido dos aplicativos .NET MAUI (interface do usuário de aplicativo multiplataforma) . Coletivamente, essas técnicas podem reduzir consideravelmente a quantidade de trabalho que está sendo executada por uma CPU e a quantidade de memória consumida por um aplicativo.
Usar um criador de perfil
Ao desenvolver um aplicativo, é importante tentar apenas otimizar o código depois que ele tiver sido perfilado. A criação de perfil é uma técnica para determinar onde as otimizações de código terão o maior efeito na redução de problemas de desempenho. O perfilador monitora o uso de memória do aplicativo e registra o tempo de execução dos métodos do aplicativo. Esses dados ajudam a navegar pelos caminhos de execução do aplicativo e pelo custo de execução do código, para que as melhores oportunidades de otimização possam ser descobertas.
Os aplicativos .NET MAUI podem ser perfilados usando dotnet-trace
no Android, iOS e Mac e Windows e com o PerfView no Windows. Para obter mais informações, consulte perfilagem de aplicativos .NET MAUI.
As seguintes práticas recomendadas devem ser seguidas ao fazer o perfil de um aplicativo:
- Evite criar perfil de um aplicativo em um simulador, pois o simulador pode distorcer o desempenho do aplicativo.
- Idealmente, a criação de perfil deve ser executada em uma variedade de dispositivos, pois fazer medidas de desempenho em um dispositivo nem sempre mostrará as características de desempenho de outros dispositivos. No entanto, no mínimo, a criação de perfil deve ser executada em um dispositivo que tenha a especificação mais baixa prevista.
- Feche todos os outros aplicativos para garantir que o impacto total do aplicativo que está sendo perfilado esteja sendo medido, em vez dos outros aplicativos.
Usar associações compiladas
As associações compiladas melhoram o desempenho da vinculação de dados em aplicativos .NET MAUI ao resolver expressões de associação em tempo de compilação, em vez de em tempo de execução com reflexão. Compilar uma expressão de associação gera um código compilado que normalmente resolve uma associação 8 a 20 vezes mais rápida do que usar uma associação clássica. Para obter mais informações, consulte vinculações compiladas.
Reduzir associações desnecessárias
Não use associações para conteúdo que possa ser facilmente definido estaticamente. Não há nenhuma vantagem em associar dados que não precisam ser associados, pois as associações não são econômicas. Por exemplo, definir Button.Text = "Accept"
tem menos sobrecarga do que associar Button.Text a uma propriedade string
viewmodel com o valor "Accept".
Escolher o layout correto
Um layout capaz de exibir vários filhos, mas que só tem um único filho, é um desperdício. Por exemplo, o exemplo a seguir mostra um VerticalStackLayout com um único filho:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<VerticalStackLayout>
<Image Source="waterfront.jpg" />
</VerticalStackLayout>
</ContentPage>
Isso é um desperdício e o elemento VerticalStackLayout deve ser removido, conforme mostrado no exemplo a seguir:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<Image Source="waterfront.jpg" />
</ContentPage>
Além disso, não tente reproduzir a aparência de um layout específico usando combinações de outros layouts, pois isso resulta em cálculos de layout desnecessários sendo executados. Por exemplo, não tente reproduzir um layout de Grid usando uma combinação de elementos HorizontalStackLayout. O exemplo a seguir mostra um exemplo dessa prática incorreta:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<VerticalStackLayout>
<HorizontalStackLayout>
<Label Text="Name:" />
<Entry Placeholder="Enter your name" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Age:" />
<Entry Placeholder="Enter your age" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Occupation:" />
<Entry Placeholder="Enter your occupation" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Address:" />
<Entry Placeholder="Enter your address" />
</HorizontalStackLayout>
</VerticalStackLayout>
</ContentPage>
Isso é um desperdício porque são executados cálculos de layout desnecessários. Em vez disso, o layout desejado pode ser melhor obtido usando um Grid, conforme mostrado no exemplo a seguir:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<Grid ColumnDefinitions="100,*"
RowDefinitions="30,30,30,30">
<Label Text="Name:" />
<Entry Grid.Column="1"
Placeholder="Enter your name" />
<Label Grid.Row="1"
Text="Age:" />
<Entry Grid.Row="1"
Grid.Column="1"
Placeholder="Enter your age" />
<Label Grid.Row="2"
Text="Occupation:" />
<Entry Grid.Row="2"
Grid.Column="1"
Placeholder="Enter your occupation" />
<Label Grid.Row="3"
Text="Address:" />
<Entry Grid.Row="3"
Grid.Column="1"
Placeholder="Enter your address" />
</Grid>
</ContentPage>
Otimizar recursos de imagem
As imagens são alguns dos recursos mais caros que os aplicativos usam e geralmente são capturadas em altas resoluções. Embora isso crie imagens vibrantes cheias de detalhes, os aplicativos que exibem essas imagens normalmente exigem mais uso da CPU para decodificar a imagem e mais memória para armazenar a imagem decodificada. É um desperdício decodificar uma imagem de alta resolução na memória quando ela será dimensionada para um tamanho menor para exibição. Em vez disso, reduza o uso da CPU e o volume de memória criando versões de imagens armazenadas próximas aos tamanhos de exibição previstos. Por exemplo, uma imagem exibida em um modo de exibição de lista provavelmente deve ser uma resolução menor do que uma imagem exibida em tela inteira.
Além disso, as imagens só devem ser criadas quando necessário e devem ser liberadas assim que o aplicativo não precisar mais delas. Por exemplo, se um aplicativo estiver exibindo uma imagem lendo seus dados de um fluxo, verifique se o fluxo é criado somente quando for necessário e verifique se o fluxo é liberado quando ele não for mais necessário. Isso pode ser feito criando o fluxo quando a página é criada ou quando o evento Page.Appearing é acionado e descartando o fluxo quando o evento Page.Disappearing é acionado.
Ao baixar uma imagem para exibição com o método ImageSource.FromUri(Uri), verifique se a imagem baixada é armazenada em cache por um período adequado de tempo. Para obter mais informações, consulte cache de imagem.
Reduzir o número de elementos em uma página
Reduzir o número de elementos em uma página fará com que a página seja renderizada mais rapidamente. Há duas técnicas principais para conseguir isso. A primeira é ocultar elementos que não estão visíveis. A propriedade IsVisible de cada elemento determina se o elemento deve estar visível na tela. Se um elemento não estiver visível porque está oculto atrás de outros elementos, remova o elemento ou defina sua propriedade IsVisible
como false
. Definir a propriedade IsVisible
em um elemento para false
mantém o elemento na árvore visual, mas exclui-o dos cálculos de renderização e layout.
A segunda técnica é remover elementos desnecessários. Por exemplo, o seguinte mostra um layout de página contendo vários elementos Label:
<VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Hello" />
</VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Welcome to the App!" />
</VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Downloading Data..." />
</VerticalStackLayout>
</VerticalStackLayout>
O mesmo layout de página pode ser mantido com uma contagem de elementos reduzida, conforme mostrado no exemplo a seguir:
<VerticalStackLayout Padding="20,35,20,20"
Spacing="25">
<Label Text="Hello" />
<Label Text="Welcome to the App!" />
<Label Text="Downloading Data..." />
</VerticalStackLayout>
Reduzir o tamanho do dicionário de recursos do aplicativo
Todos os recursos usados em todo o aplicativo devem ser armazenados no dicionário de recursos do aplicativo para evitar a duplicação. Isso ajudará a reduzir a quantidade de XAML que precisa ser analisada em todo o aplicativo. O exemplo a seguir mostra o recurso HeadingLabelStyle
, que é usado em todo o aplicativo e, portanto, é definido no dicionário de recursos do aplicativo:
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.App">
<Application.Resources>
<Style x:Key="HeadingLabelStyle"
TargetType="Label">
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="FontSize"
Value="Large" />
<Setter Property="TextColor"
Value="Red" />
</Style>
</Application.Resources>
</Application>
No entanto, o XAML específico a uma página não deve ser incluído no dicionário de recursos do aplicativo, pois os recursos serão processados na inicialização do aplicativo em vez de quando exigidos por uma página. Se um recurso for usado por uma página que não seja a página de inicialização, ele deverá ser colocado no dicionário de recursos dessa página, ajudando, portanto, a reduzir o XAML analisado quando o aplicativo é iniciado. O exemplo a seguir mostra o recurso HeadingLabelStyle
, que está apenas em uma única página e, portanto, é definido no dicionário de recursos da página:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<ContentPage.Resources>
<Style x:Key="HeadingLabelStyle"
TargetType="Label">
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="FontSize"
Value="Large" />
<Setter Property="TextColor"
Value="Red" />
</Style>
</ContentPage.Resources>
...
</ContentPage>
Para obter mais informações sobre recursos de aplicativo, consulte Estilize aplicativos usando XAML.
Reduzir o tamanho do aplicativo
Quando o .NET MAUI cria seu aplicativo, um vinculador chamado ILLink pode ser usado para reduzir o tamanho geral do aplicativo. O ILLink reduz o tamanho analisando o código intermediário produzido pelo compilador. Ele remove métodos, propriedades, campos, eventos, structs e classes não utilizados para produzir um aplicativo que contém apenas dependências de código e assembly necessárias para executar o aplicativo.
Para obter mais informações sobre como configurar o comportamento do vinculador, consulte Vincular um aplicativo Android, Vincular um aplicativo iOSe Vincular um aplicativo Mac Catalyst.
Reduzir o período de ativação do aplicativo
Todos os aplicativos têm um período de ativação , que é o tempo entre quando o aplicativo é iniciado e quando o aplicativo está pronto para uso. Esse período de ativação fornece aos usuários a primeira impressão do aplicativo e, portanto, é importante reduzir o período de ativação e a percepção do usuário sobre ele, para que eles obtenham uma primeira impressão favorável do aplicativo.
Antes que um aplicativo exiba sua interface do usuário inicial, ele deve fornecer uma tela inicial para indicar ao usuário que o aplicativo está sendo iniciado. Se o aplicativo não puder exibir rapidamente sua interface do usuário inicial, a tela inicial deverá ser usada para informar o usuário sobre o progresso durante o período de ativação, para oferecer garantia de que o aplicativo não foi travado. Essa garantia pode ser uma barra de progresso ou um controle semelhante.
Durante o período de ativação, os aplicativos executam a lógica de ativação, que geralmente inclui o carregamento e o processamento de recursos. O período de ativação pode ser reduzido garantindo que os recursos necessários sejam empacotados no aplicativo, em vez de serem recuperados remotamente. Por exemplo, em algumas circunstâncias, pode ser apropriado durante o período de ativação carregar dados de espaço reservado armazenados localmente. Depois que a interface do usuário inicial for exibida e o usuário puder interagir com o aplicativo, os dados de placeholder podem ser progressivamente substituídos por dados de uma fonte remota. Além disso, a lógica de ativação do aplicativo deve executar apenas o trabalho necessário para permitir que o usuário comece a usar o aplicativo. Isso pode ajudar se ele atrasar o carregamento de assemblies adicionais, pois os assemblies são carregados na primeira vez em que são usados.
Escolha um contêiner de injeção de dependência com cuidado
Os contêineres de injeção de dependência introduzem limitações adicionais de desempenho em aplicativos móveis. Registrar e resolver tipos com um contêiner tem um custo de desempenho devido ao uso de reflexão do contêiner para criar cada tipo, especialmente se as dependências estiverem sendo reconstruídas para cada navegação de página no aplicativo. Se houver muitas dependências ou dependências profundas, o custo de criação poderá aumentar significativamente. Além disso, o registro de tipo, que geralmente ocorre durante a inicialização do aplicativo, pode ter um impacto perceptível no tempo de inicialização, dependendo do contêiner que está sendo usado. Para obter mais informações sobre a injeção de dependência em aplicativos .NET MAUI, consulte Injeção de dependência.
Como alternativa, a injeção de dependência pode se tornar mais eficaz implementando-a manualmente usando fábricas.
Criar aplicativos shell
Os aplicativos .NET MAUI Shell fornecem uma experiência de navegação opinativa com base em submenus e guias. Se a experiência do usuário do aplicativo puder ser implementada com o Shell, será benéfico fazer isso. Os aplicativos Shell ajudam a evitar uma experiência de inicialização ruim, pois as páginas são criadas sob demanda em resposta à navegação em vez de na inicialização do aplicativo, o que ocorre com aplicativos que usam um TabbedPage. Para obter mais informações, consulte visão geral do Shell.
Otimizar o desempenho do ListView
Ao usar ListView, há várias experiências do usuário que devem ser otimizadas:
- Inicialização – o intervalo de tempo que começa quando o controle é criado e termina quando os itens são exibidos na tela.
- Rolagem – a capacidade de rolar pela lista e garantir que a interface do usuário não atrase em relação aos gestos de toque.
- interação para adicionar, excluir e selecionar itens.
O controle ListView requer um aplicativo para fornecer dados e modelos de célula. Como isso é alcançado terá um grande impacto no desempenho do controle. Para obter mais informações, consulte dados de cache.
Usar programação assíncrona
A capacidade de resposta geral do seu aplicativo pode ser aprimorada e os gargalos de desempenho geralmente são evitados usando programação assíncrona. No .NET, o TAP (padrão assíncrono baseado em tarefa)
Fundamentos
As seguintes diretrizes gerais devem ser seguidas ao usar o TAP:
- Entenda o ciclo de vida da tarefa, que é representado pela enumeração
TaskStatus
. Para obter mais informações, consulte O significado de Status da Tarefa e Status da Tarefa. - Use o método
Task.WhenAll
para aguardar assíncronamente a conclusão de várias operações assíncronas, em vez de executar uma série de operações assíncronas individualmente comawait
. Para obter mais informações, consulte Task.WhenAll. - Use o método
Task.WhenAny
para aguardar de forma assíncrona uma das várias operações assíncronas para concluir. Para obter mais informações, consulte Task.WhenAny. - Use o método
Task.Delay
para produzir um objetoTask
que seja concluído após o horário especificado. Isso é útil para cenários como sondagem de dados e atraso no tratamento da entrada do usuário por um tempo predeterminado. Para obter mais informações, consulte Task.Delay. - Execute operações de CPU síncronas intensivas no pool de threads com o método
Task.Run
. Esse método é um atalho para o métodoTaskFactory.StartNew
, com os argumentos mais ideais definidos. Para obter mais informações, consulte Task.Run. - Evite tentar criar construtores assíncronos. Em vez disso, use eventos de ciclo de vida ou lógica de inicialização separada para
await
corretamente qualquer inicialização. Para obter mais informações, consulte construtores assíncronos no blog.stephencleary.com. - Use o padrão de tarefa lento para evitar aguardar a conclusão de operações assíncronas durante a inicialização do aplicativo. Para obter mais informações, consulte AsyncLazy.
- Crie um wrapper de tarefa para operações assíncronas existentes, que não usam o TAP, criando
TaskCompletionSource<T>
objetos. Esses objetos aproveitam os benefícios da programaçãoTask
e permitem controlar a durabilidade e a finalização doTask
associado. Para obter mais informações, consulte The Nature of TaskCompletionSource. - Retorne um objeto
Task
, em vez de retornar um objetoTask
aguardado, quando não houver necessidade de processar o resultado de uma operação assíncrona. Isso tem um desempenho melhor devido à menor alternância de contexto. - Use a biblioteca de fluxo de dados TPL (Biblioteca Paralela de Tarefas) em cenários como o processamento de dados à medida que eles estiverem disponíveis ou quando você tiver várias operações que devem se comunicar entre si de forma assíncrona. Para obter mais informações, consulte Dataflow (Biblioteca Paralela da Tarefa).
IU (Interface do Usuário)
As diretrizes a seguir devem ser seguidas ao usar o TAP com controles de interface do usuário:
Chame uma versão assíncrona de uma API, se ela estiver disponível. Isso manterá a linha de execução da interface do usuário desbloqueada, o que ajudará a melhorar a experiência do usuário com o aplicativo.
Atualize os elementos da interface do usuário com dados de operações assíncronas no thread da interface do usuário, para evitar que exceções sejam geradas. No entanto, as atualizações na propriedade
ListView.ItemsSource
serão automaticamente encaminhadas para o thread da interface do usuário. Para obter informações sobre como determinar se o código está em execução no thread da interface do usuário, consulte Criar um thread no thread da interface do usuário.Importante
Todas as propriedades de controle atualizadas por meio da associação de dados serão automaticamente transferidas para a linha de execução da interface do usuário.
Tratamento de erros
As seguintes diretrizes de tratamento de erros devem ser seguidas ao usar o TAP:
- Saiba mais sobre o tratamento de exceções assíncronas. Exceções sem tratamento geradas por código que está sendo executado de forma assíncrona são propagadas de volta para o thread de chamada, exceto em determinados cenários. Para obter mais informações, consulte Tratamento de Exceções (Biblioteca Paralela de Tarefas).
- Evite criar métodos
async void
e, em vez disso, crie métodosasync Task
. Elas facilitam o tratamento de erros, a composição e a capacidade de teste. A exceção a essa diretriz são manipuladores de eventos assíncronos, que devem retornarvoid
. Para obter mais informações, consulte EvitarNulo Assíncrono. - Não misture o bloqueio e o código assíncrono chamando os métodos
Task.Wait
,Task.Result
ouGetAwaiter().GetResult
, pois eles podem resultar em deadlock. No entanto, se essa diretriz precisar ser violada, a abordagem preferencial será chamar o métodoGetAwaiter().GetResult
porque ele preserva as exceções de tarefa. Para obter mais informações, consulte Async All the Way e Task Exception Handling in .NET 4.5. - Use o método
ConfigureAwait
sempre que possível para criar código sem contexto. O código sem contexto tem melhor desempenho para aplicativos móveis e é uma técnica útil para evitar deadlock ao trabalhar com uma base de código parcialmente assíncrona. Para obter mais informações, consulte Configurar Contexto. - Use tarefas de continuação para funcionalidades como lidar com exceções geradas pela operação assíncrona anterior e cancelar uma continuação antes de ser iniciada ou enquanto ela estiver em execução. Para obter mais informações, consulte Encadeamento de tarefas usando tarefas contínuas.
- Use uma implementação assíncrona ICommand quando operações assíncronas são invocadas do ICommand. Isso garante que quaisquer exceções na lógica de comando assíncrona possam ser tratadas. Para obter mais informações, consulte Programação Assíncrona: Padrões para aplicativos MVVM assíncronos: comandos.
Atrasar o custo de criação de objetos
A inicialização lenta pode ser usada para adiar a criação de um objeto até que ele seja usado pela primeira vez. Essa técnica é usada principalmente para melhorar o desempenho, evitar a computação e reduzir os requisitos de memória.
Considere usar a inicialização lenta para objetos que são caros de criar nos seguintes cenários:
- O aplicativo pode não usar o objeto.
- Outras operações caras devem ser concluídas antes que o objeto seja criado.
A classe Lazy<T>
é usada para definir um tipo inicializado lento, conforme mostrado no exemplo a seguir:
void ProcessData(bool dataRequired = false)
{
Lazy<double> data = new Lazy<double>(() =>
{
return ParallelEnumerable.Range(0, 1000)
.Select(d => Compute(d))
.Aggregate((x, y) => x + y);
});
if (dataRequired)
{
if (data.Value > 90)
{
...
}
}
}
double Compute(double x)
{
...
}
A inicialização lenta ocorre na primeira vez em que a propriedade Lazy<T>.Value
é acessada. O tipo encapsulado é criado e retornado no primeiro acesso e armazenado para acessos futuros.
Para obter mais informações sobre a inicialização lenta, consulte Inicialização Lenta.
Liberar recursos IDisposable
A interface IDisposable
fornece um mecanismo para liberar recursos. Ele fornece um método Dispose
que deve ser implementado para liberar explicitamente os recursos.
IDisposable
não é um destruidor e só deve ser implementado nas seguintes circunstâncias:
- Quando a classe possui recursos não gerenciados. Os recursos não gerenciados típicos que exigem a liberação incluem arquivos, fluxos e conexões de rede.
- Quando a classe possui recursos
IDisposable
gerenciados.
Os consumidores de tipo podem chamar a implementação de IDisposable.Dispose
para liberar recursos quando a instância não for mais necessária. Há duas abordagens para fazer isso:
- Encapsulando o objeto
IDisposable
em uma instruçãousing
. - Encapsulando a chamada para
IDisposable.Dispose
em um bloco detry
/finally
.
Encapsular o objeto IDisposable em uma instrução using
O exemplo a seguir mostra como encapsular um objeto IDisposable
em uma instrução using
:
public void ReadText(string filename)
{
string text;
using (StreamReader reader = new StreamReader(filename))
{
text = reader.ReadToEnd();
}
...
}
A classe StreamReader
implementa IDisposable
e a instrução using
fornece uma sintaxe conveniente que chama o método StreamReader.Dispose
no objeto StreamReader
antes de sair do escopo. No bloco using
, o objeto StreamReader
é somente leitura e não pode ser reatribuído. A instrução using
também garante que o método Dispose
seja chamado mesmo se ocorrer uma exceção, pois o compilador implementa a IL (linguagem intermediária) para um bloco try
/finally
.
Encapsular a chamada para IDisposable.Dispose em um bloco try/finally
O exemplo a seguir mostra como encapsular a chamada para IDisposable.Dispose
em um bloco de try
/finally
:
public void ReadText(string filename)
{
string text;
StreamReader reader = null;
try
{
reader = new StreamReader(filename);
text = reader.ReadToEnd();
}
finally
{
if (reader != null)
reader.Dispose();
}
...
}
A classe StreamReader
implementa IDisposable
e o bloco finally
chama o método StreamReader.Dispose
para liberar o recurso. Para obter mais informações, consulte Interface IDisposable.
Cancelar assinatura de eventos
Para evitar vazamentos de memória, os eventos devem ser desinscritos antes que o objeto assinante seja descartado. Até que o evento seja cancelado, o delegado do evento no objeto de publicação tem uma referência ao delegado que encapsula o manipulador de eventos do assinante. Desde que o objeto de publicação mantenha essa referência, a coleta de lixo não recuperará a memória do objeto do assinante.
O exemplo a seguir mostra como cancelar a assinatura de um evento:
public class Publisher
{
public event EventHandler MyEvent;
public void OnMyEventFires()
{
if (MyEvent != null)
MyEvent(this, EventArgs.Empty);
}
}
public class Subscriber : IDisposable
{
readonly Publisher _publisher;
public Subscriber(Publisher publish)
{
_publisher = publish;
_publisher.MyEvent += OnMyEventFires;
}
void OnMyEventFires(object sender, EventArgs e)
{
Debug.WriteLine("The publisher notified the subscriber of an event");
}
public void Dispose()
{
_publisher.MyEvent -= OnMyEventFires;
}
}
A classe Subscriber
cancela a assinatura do evento em seu método Dispose
.
Os ciclos de referência também podem ocorrer ao usar manipuladores de eventos e sintaxe lambda, pois as expressões lambda podem referenciar e manter os objetos ativos. Portanto, uma referência ao método anônimo pode ser armazenada em um campo e usada para cancelar a assinatura do evento, conforme mostrado no exemplo a seguir:
public class Subscriber : IDisposable
{
readonly Publisher _publisher;
EventHandler _handler;
public Subscriber(Publisher publish)
{
_publisher = publish;
_handler = (sender, e) =>
{
Debug.WriteLine("The publisher notified the subscriber of an event");
};
_publisher.MyEvent += _handler;
}
public void Dispose()
{
_publisher.MyEvent -= _handler;
}
}
O campo _handler
mantém a referência ao método anônimo e é usado para assinatura de evento e cancelamento da assinatura.
Evitar referências circulares fortes no iOS e no Mac Catalyst
Em algumas situações, é possível criar ciclos de referência fortes que poderiam impedir que os objetos tenham sua memória recuperada pelo coletor de lixo. Por exemplo, considere o caso em que uma subclasse derivada de NSObject, como uma classe que herda de UIView, é adicionada a um contêiner derivado de NSObject
e é fortemente referenciada de Objective-C, conforme mostrado no exemplo a seguir:
class Container : UIView
{
public void Poke()
{
// Call this method to poke this object
}
}
class MyView : UIView
{
Container _parent;
public MyView(Container parent)
{
_parent = parent;
}
void PokeParent()
{
_parent.Poke();
}
}
var container = new Container();
container.AddSubview(new MyView(container));
Quando esse código cria a instância Container
, o objeto C# terá uma referência forte a um objeto Objective-C. Da mesma forma, a instância de MyView
também terá uma referência forte a um objeto Objective-C.
Além disso, a chamada para container.AddSubview
aumentará a contagem de referência na instância não gerenciada de MyView
. Quando isso acontece, o runtime do .NET para iOS cria uma instância GCHandle
para manter o objeto MyView
no código gerenciado ativo, pois não há garantia de que quaisquer objetos gerenciados manterão uma referência a ele. De uma perspectiva de código gerenciado, o objeto MyView
seria recuperado após a chamada AddSubview(UIView) se não fosse pelo GCHandle
.
O objeto MyView
não gerenciado terá um GCHandle
apontando para o objeto gerenciado, conhecido como um vínculo forte . O objeto gerenciado conterá uma referência à instância de Container
. Por sua vez, a instância de Container
terá uma referência gerenciada ao objeto MyView
.
Em circunstâncias em que um objeto contido mantém um link para seu contêiner, há várias opções disponíveis para lidar com a referência circular:
- Evite a referência circular mantendo uma referência fraca para o contêiner.
- Chame
Dispose
em objetos. - Interromper manualmente o ciclo definindo o link para o contêiner como
null
. - Remova manualmente o objeto contido do contêiner.
Usar referências fracas
Uma maneira de evitar um ciclo é usar uma referência fraca do filho para o pai. Por exemplo, o código acima pode ser mostrado no exemplo a seguir:
class Container : UIView
{
public void Poke()
{
// Call this method to poke this object
}
}
class MyView : UIView
{
WeakReference<Container> _weakParent;
public MyView(Container parent)
{
_weakParent = new WeakReference<Container>(parent);
}
void PokeParent()
{
if (weakParent.TryGetTarget (out var parent))
parent.Poke();
}
}
var container = new Container();
container.AddSubview(new MyView container));
Aqui, o objeto contido não manterá o pai vivo. No entanto, o pai mantém a criança viva através da chamada para container.AddSubView
.
Isso também acontece nas APIs do iOS que usam o padrão delegado ou de fonte de dados, em que uma classe de par contém a implementação. Por exemplo, ao definir a propriedade Delegate ou a DataSource na classe UITableView.
No caso de classes que são criadas puramente para implementar um protocolo, por exemplo, o IUITableViewDataSource, o que você pode fazer é, em vez de criar uma subclasse, basta implementar a interface na classe e substituir o método e atribuir a propriedade DataSource
a this
.
Descartar objetos com referências fortes
Se existir uma referência forte e for difícil remover a dependência, faça com que um método Dispose
anule o ponteiro pai.
Para contêineres, substitua o método Dispose
para remover os objetos contidos, conforme mostrado no exemplo a seguir:
class MyContainer : UIView
{
public override void Dispose()
{
// Brute force, remove everything
foreach (var view in Subviews)
{
view.RemoveFromSuperview();
}
base.Dispose();
}
}
Para um objeto filho que mantém uma referência forte ao pai, desmarque a referência ao pai na implementação do Dispose
:
class MyChild : UIView
{
MyContainer _container;
public MyChild(MyContainer container)
{
_container = container;
}
public override void Dispose()
{
_container = null;
}
}