Melhorar o desempenho do aplicativo

O baixo desempenho do aplicativo se apresenta de várias maneiras. Ele pode fazer com que um aplicativo pareça não responder, pode causar rolagem lenta e pode reduzir a vida útil da bateria do dispositivo. No entanto, a otimização do desempenho engloba mais do que apenas a implementação de 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 o usuário de realizar outras atividades pode ajudar a melhorar a experiência do usuário.

Há muitas técnicas para aumentar o desempenho e o desempenho percebido de aplicativos .NET Multi-platform App UI (.NET MAUI). Coletivamente, essas técnicas podem reduzir muito a quantidade de trabalho que está sendo executado por uma CPU e a quantidade de memória consumida por um aplicativo.

Usar um criador de perfil

Ao desenvolver um aplicativo, é importante tentar otimizar o código apenas depois que ele tiver sido perfilado. 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 criador de perfil controla o uso de memória do aplicativo e registra o tempo de execução dos métodos no 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 no Windows, e com o PerfView no Windows. Para obter mais informações, consulte Definindo o perfil de aplicativos MAUI do .NET.

As melhores práticas a seguir são recomendadas ao analisar um aplicativo:

  • Evite criar o 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 tomar medidas de desempenho em um único dispositivo nem sempre mostrará as características de desempenho de outros dispositivos. No entanto, no mínimo, de criação de perfil deve ser executada em um dispositivo que tem a menor especificação antecipada.
  • 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 resolvendo expressões de vinculação em tempo de compilação, em vez de em tempo de execução com reflexão. Quando você compila uma expressão de associação, é gerado um código compilado que, normalmente, resolve uma associação de 8 a 20 vezes mais rápido do que uma associação clássica. Para obter mais informações, consulte Associações compiladas.

Reduzir associações desnecessárias

Não use associações para conteúdo que pode ser facilmente definido estaticamente. Não há nenhuma vantagem em associar dados que não precisam ser associados, pois associações não são econômicas. Por exemplo, a configuração tem menos sobrecarga do que a vinculação Button.Text a Button.Text = "Accept" uma propriedade viewmodel string com o valor "Accept".

Escolher o layout correto

Um layout que é capaz de exibir vários filhos, mas que tem apenas um único filho, é um desperdício. Por exemplo, o exemplo a seguir mostra um com um VerticalStackLayout ú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 VerticalStackLayout elemento deve ser removido, como 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 na execução de cálculos de layout desnecessários. Por exemplo, não tente reproduzir um Grid layout usando uma combinação de HorizontalStackLayout elementos. O exemplo a seguir mostra um exemplo dessa má prática:

<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 cálculos de layout desnecessário são executados. Em vez disso, o layout desejado pode ser melhor alcançado usando um Grid, como 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 os 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. É dispendioso decodificar uma imagem de alta resolução na memória sendo que ele será reduzido para um tamanho menor para exibição. Em vez disso, reduza o uso da CPU e o espaço ocupado pela memória criando versões de imagens armazenadas próximas aos tamanhos de exibição previstos. Por exemplo, uma imagem exibida em uma exibição de lista provavelmente deve ter 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, certifique-se de que o fluxo seja criado somente quando for necessário e certifique-se de que o fluxo seja liberado quando 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, em seguida, descartando o fluxo quando o evento Page.Disappearing é acionado.

Ao fazer download de uma imagem para exibição com o ImageSource.FromUri(Uri) método, certifique-se de que a imagem baixada seja armazenada em cache por um período de tempo adequado. Para obter mais informações, consulte Cache de imagem.

Reduzir o tamanho da árvore visual

Reduzir o número de elementos em uma página tornará a renderização da página mais rápida. Há duas técnicas principais para realizar essa tarefa. A primeira é ocultar elementos que não estão visíveis. A propriedade IsVisible de cada elemento determina se o elemento deve fazer parte da árvore visual ou não. Portanto, 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.

A segunda técnica é remover elementos desnecessários. Por exemplo, o seguinte mostra um layout de página contendo vários Label elementos:

<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 reduzida de elementos, 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 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, que é usado em todo o HeadingLabelStyle 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 de uma página não deve ser incluído no dicionário de recursos do aplicativo, pois os recursos serão analisados na inicialização do aplicativo em vez de quando exigido 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 a reduzir o XAML analisado quando o aplicativo é iniciado. O exemplo a seguir mostra o HeadingLabelStyle recurso, 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 Estilizar 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. ILLink reduz o tamanho analisando o código intermediário produzido pelo compilador. Ele remove métodos, propriedades, campos, eventos, estruturas 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 Vinculando um aplicativo Android, Vinculando um aplicativo iOS e Vinculando 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 sua primeira impressão do aplicativo e, portanto, é importante reduzir o período de ativação e a percepção do usuário sobre ele, a fim de que eles obtenham uma primeira impressão favorável do aplicativo.

Antes de um aplicativo exibir sua interface do usuário inicial, ele deve fornecer uma tela inicial para indicar ao usuário que o aplicativo está iniciando. Se o aplicativo não puder exibir rapidamente sua interface do usuário inicial, a tela inicial deve 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 travou. 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. Em seguida, depois que a interface do usuário inicial é exibida e o usuário é capaz de interagir com o aplicativo, os dados de espaço reservado podem ser substituídos progressivamente de uma fonte remota. Além disso, a lógica de ativação do aplicativo só deve executar o trabalho necessário para permitir que o usuário comece a usar o aplicativo. Isso pode ajudar se atrasar o carregamento de assemblies adicionais, já que assemblies são carregados na primeira vez em que eles são usados.

Escolher bem um contêiner de injeção de dependência

Os contêineres de injeção de dependência introduzem restrições de desempenho adicionais em aplicativos móveis. Efetuar o registro e a resolução de tipos usando um contêiner tem um custo de desempenho devido ao uso da reflexão pelo 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 se elas forem profundas, o custo da 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 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 por meio da implementação manual usando fábricas.

Criar aplicativos do 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 fazê-lo. Os aplicativos de shell ajudam a evitar uma experiência de inicialização ruim, porque as páginas são criadas sob demanda em resposta à navegação e não na inicialização do aplicativo, o que ocorre com aplicativos que usam um TabbedPagearquivo . Para obter mais informações, consulte Visão geral do Shell.

Otimizar o desempenho da ListView

Ao usar ListView, há várias experiências de usuário que devem ser otimizadas:

  • Inicialização – o intervalo de tempo que começa quando o controle é criado e termina quando itens são mostrados na tela.
  • Rolagem – a capacidade de rolar pela lista e garantir que a interface do usuário não fique atrasada com relação a gestos de toque.
  • Interação para adicionar, excluir e selecionar itens.

O ListView controle requer um aplicativo para fornecer dados e modelos de célula. Como isso é feito terá um grande impacto sobre o 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 evitados usando a programação assíncrona. No .NET, o TAP (Padrão Assíncrono Baseado em Tarefa) é o padrão de design recomendado para operações assíncronas. No entanto, o uso incorreto do TAP pode resultar em aplicativos sem desempenho.

Conceitos básicos

As seguintes orientações gerais devem ser seguidas ao utilizar a TAP:

  • Compreenda o ciclo de vida da tarefa, que é representado pela TaskStatus enumeração. Para obter mais informações, consulte O significado de TaskStatus e Status da tarefa.
  • Use o Task.WhenAll método para aguardar de forma assíncrona a conclusão de várias operações assíncronas, em vez de individualmente await uma série de operações assíncronas. Para obter mais informações, consulte Task.WhenAll.
  • Use o método para aguardar de forma assíncrona Task.WhenAny a conclusão de uma das várias operações assíncronas. Para obter mais informações, consulte Task.WhenAny.
  • Use o método para produzir um Task objeto que termina após o Task.Delay tempo 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 síncronas intensivas de CPU no pool de threads com o Task.Run método. Esse método é um atalho para o TaskFactory.StartNew método, com os argumentos mais otimizados 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 corrigir await qualquer inicialização. Para obter mais informações, consulte Construtores assíncronos no blog.stephencleary.com.
  • Use o padrão de tarefa lenta 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 tarefas para operações assíncronas existentes, que não usam o TAP, criando TaskCompletionSource<T> objetos. Esses objetos obtêm os benefícios da programação e permitem que você controle o tempo de Task vida e a conclusão do Task. Para obter mais informações, consulte A natureza de TaskCompletionSource.
  • Retorne um objeto, em vez de retornar um Task objeto aguardado Task , quando não houver necessidade de processar o resultado de uma operação assíncrona. Isso é mais eficiente devido à menor troca de contexto sendo executada.
  • Use a biblioteca de fluxo de dados TPL (Biblioteca Paralela de Tarefas) em cenários como o processamento de dados à medida que eles se tornam 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 Fluxo de dados (Biblioteca paralela de tarefas).

UI

As seguintes diretrizes 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á o thread da interface do usuário desbloqueado, 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 lançadas. No entanto, as atualizações para a ListView.ItemsSource propriedade serão automaticamente empacotadas para o thread da interface do usuário. Para obter informações sobre como determinar se o código está sendo executado no thread da interface do usuário, consulte Criar um thread no thread da interface do usuário.

    Importante

    Todas as propriedades de controle que são atualizadas por meio de vinculação de dados serão automaticamente empacotadas para o thread 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 assíncrono de exceções. As exceções não tratadas que são lançadas pelo 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 async void métodos e, em vez disso, crie async Task métodos. Isso facilita o tratamento de erros, a capacidade de composição e a estabilidade. A exceção a essa diretriz são os manipuladores de eventos assíncronos, que devem retornar void. Para obter mais informações, consulte Evitar o Async Void.
  • Não misture bloqueio e código assíncrono chamando os Task.Waitmétodos , ou , Task.Resultpois GetAwaiter().GetResult eles podem resultar na ocorrência de deadlock. No entanto, se essa diretriz precisar ser violada, a abordagem preferida é chamar o GetAwaiter().GetResult método porque ele preserva as exceções de tarefa. Para obter mais informações, consulte Async All the Way e Task Exception Handling no .NET 4.5.
  • Use o ConfigureAwait método sempre que possível, para criar código livre de contexto. O código livre de contexto tem melhor desempenho para aplicativos móveis e é uma técnica útil para evitar o 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 manipular exceções geradas pela operação assíncrona anterior e cancelar uma continuação antes de iniciar ou enquanto estiver em execução. Para obter mais informações, consulte Encadeando tarefas usando tarefas contínuas.
  • Use uma implementação assíncrona quando operações assíncronas ICommand forem chamadas a ICommandpartir do . Isso garante que quaisquer exceções na lógica de comando assíncrona possam ser manipuladas. 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 o uso de 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 Lazy<T> classe é usada para definir um tipo inicializado preguiçoso, 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 que a propriedade Lazy<T>.Value é acessada. O tipo encapsulado é criado e retornado no primeiro acesso, então é armazenado para eventuais acessos futuros.

Para obter mais informações sobre a inicialização lenta, veja 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 recursos explicitamente. IDisposable não é um destruidor e só deve ser implementado nas seguintes circunstâncias:

  • Quando a classe tem recursos não gerenciados. Recursos não gerenciados típicos que exigem liberação incluem arquivos, fluxos e conexões de rede.
  • Quando a classe tem recursos IDisposable gerenciados.

Os consumidores de tipo podem chamar a implementação IDisposable.Dispose para liberar recursos quando a instância não é mais necessária. Há duas abordagens para fazer isso:

  • Encapsulando o objeto IDisposable em uma instrução using.
  • Encapsulando a chamada IDisposable.Dispose em um bloco try/finally.

Encapsular o objeto IDisposable em uma instrução using

O exemplo a seguir mostra como encapsular um IDisposable objeto em uma using instrução:

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 que ele saia do escopo. Dentro do bloco using, o objeto StreamReader é somente leitura e não pode ser reatribuído. A instrução using garante que o método Dispose seja chamado mesmo se ocorrer uma exceção, pois o compilador implementa a IL (linguagem intermediária) em 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 try/finally bloco:

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, veja Interface IDisposable.

Cancelar inscrição de eventos

Para evitar vazamentos de memória, os eventos devem ser cancelados antes que o objeto do assinante seja descartado. Até que a assinatura do evento seja cancelada, o delegado para o evento no objeto de publicação tem uma referência para o delegado que encapsula o manipulador de eventos do assinante. Desde que o objeto de publicação contenha essa referência, a coleta de lixo não recuperará a memória do objeto de assinante.

O exemplo a seguir mostra como cancelar a inscrição 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.

Ciclos de referência também podem ocorrer ao usar manipuladores de eventos e sintaxe lambda, já que expressões lambda podem fazer referência a objetos e mantê-los ativos. Portanto, uma referência ao método anônimo pode ser armazenada em um campo e usada para cancelar a inscrição 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 para o método anônimo e é usado para assinatura e cancelamento de assinatura do evento.

Evite referências circulares fortes no iOS e Mac Catalyst

Em algumas situações, é possível criar ciclos de referência fortes que podem impedir que os objetos tenham sua memória recuperada pelo coletor de lixo. Por exemplo, considere o caso em que uma subclasse -derivada, como uma NSObjectclasse que herda do , é adicionada a um NSObjectcontêiner -derived e é fortemente referenciada do UIViewObjective-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, o objeto C# terá uma forte referência a Container um Objective-C objeto. Da mesma forma, a instância também terá uma forte referência a MyView um Objective-C objeto.

Além disso, a chamada para container.AddSubview aumentará a contagem de referência na instância MyView não gerenciada. Quando isso acontece, o tempo de execução do .NET iOS cria uma instância para manter o MyView objeto no código gerenciado ativo, porque não há garantia de que qualquer objeto gerenciado manterá uma GCHandle referência a ele. De uma perspectiva de código gerenciado, o objeto MyView seria recuperado depois que a chamada AddSubview(UIView) não fosse para o GCHandle.

O objeto MyView não gerenciado terá um GCHandle apontando para o objeto gerenciado, conhecido como um link forte. O objeto gerenciado terá uma referência para a instância Container. Por sua vez, a instância Container terá uma referência gerenciada ao objeto MyView.

Em casos nos quais 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 ao recipiente.
  • Chame Dispose nos objetos.
  • Interrompa manualmente o ciclo ao definir o link para o contêiner para 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 como 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 ativo. 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 de fonte de dados ou delegado, em que uma classe de mesmo nível contém a implementação. Por exemplo, ao definir a Delegate propriedade ou o DataSource na UITableView classe.

No caso das classes que são criadas somente para implementar um protocolo, por exemplo a IUITableViewDataSource, o que você pode fazer é, em vez de criar uma subclasse, apenas implementar a interface na classe e substituir o método, atribuindo a propriedade DataSource à this.

Descarte objetos com referências fortes

Se houver uma referência forte e for difícil remover a dependência, faça um método Dispose limpar o ponteiro pai.

Para contêineres, substitua o Dispose método 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 seu pai, limpe a referência ao pai na implementação Dispose:

class MyChild : UIView
{
    MyContainer _container;

    public MyChild(MyContainer container)
    {
        _container = container;
    }

    public override void Dispose()
    {
        _container = null;
    }
}