Zvýšení výkonu aplikací

Nízký výkon aplikace se prezentuje mnoha způsoby. Aplikace může vypadat jako nereagující, může způsobit pomalé posouvání a může snížit životnost baterie zařízení. Optimalizace výkonu ale zahrnuje nejen implementaci efektivního kódu. Je také potřeba zvážit uživatelské prostředí s výkonem aplikace. Například zajištění toho, aby se operace spouštěly bez blokování uživatele v provádění dalších aktivit, můžou pomoct zlepšit uživatelské prostředí.

Existuje mnoho technik pro zvýšení výkonu a vnímaného výkonu aplikací .NET pro víceplatformní aplikace (.NET MAUI). Tyto techniky společně můžou výrazně snížit množství práce prováděné procesorem a množství paměti spotřebované aplikací.

Použití profileru

Při vývoji aplikace je důležité se po profilování pouze pokusit optimalizovat kód. Profilace je technika pro určení, kde optimalizace kódu budou mít největší vliv na snížení problémů s výkonem. Profiler sleduje využití paměti aplikace a zaznamenává dobu spuštění metod v aplikaci. Tato data pomáhají procházet cesty provádění aplikace a náklady na spuštění kódu, aby bylo možné zjistit nejlepší příležitosti pro optimalizaci.

Aplikace .NET MAUI se dají profilovat pomocí dotnet-trace Androidu, iOS a Mac a Windows a perfView ve Windows. Další informace najdete v tématu Profilace aplikací .NET MAUI.

Při profilaci aplikace se doporučují následující osvědčené postupy:

  • Vyhněte se profilaci aplikace v simulátoru, protože simulátor může zkreslit výkon aplikace.
  • V ideálním případě by se profilace měla provádět na různých zařízeních, protože měření výkonu na jednom zařízení nebudou vždy zobrazovat charakteristiky výkonu jiných zařízení. Profilace by se ale měla provádět minimálně na zařízení, které má nejnižší očekávanou specifikaci.
  • Zavřete všechny ostatní aplikace, abyste měli jistotu, že se měří úplný dopad profilované aplikace, a ne ostatní aplikace.

Použití zkompilovaných vazeb

Kompilované vazby zlepšují výkon datových vazeb v aplikacích .NET MAUI překladem výrazů vazeb v době kompilace, nikoli za běhu s reflexí. Kompilace vazbového výrazu generuje zkompilovaný kód, který obvykle řeší 8–20krát rychlejší vazbu než použití klasické vazby. Další informace naleznete v tématu Kompilované vazby.

Omezení zbytečných vazeb

Nepoužívejte vazby pro obsah, který lze snadno nastavit staticky. Data vazeb, která nemusí být svázaná, nemají žádnou výhodu, protože vazby nejsou nákladově efektivní. Například nastavení Button.Text = "Accept" má menší režii než vazba Button.Text na vlastnost viewmodel string s hodnotou Accept.

Volba správného rozložení

Rozložení, které dokáže zobrazit více podřízených položek, ale má jenom jedno dítě, je plýtvání. Například následující příklad ukazuje s jedním podřízeným kódem VerticalStackLayout :

<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>

To je plýtvání a VerticalStackLayout prvek by měl být odstraněn, jak je znázorněno v následujícím příkladu:

<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>

Kromě toho se nepokoušejte reprodukovat vzhled konkrétního rozložení pomocí kombinací jiných rozložení, protože výsledkem jsou nepotřebné výpočty rozložení. Nepokoušejte se například reprodukovat Grid rozložení pomocí kombinace HorizontalStackLayout prvků. Následující příklad ukazuje příklad tohoto chybného postupu:

<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>

Je to plýtvání, protože se provádějí nepotřebné výpočty rozložení. Místo toho lze požadované rozložení lépe dosáhnout pomocí Gridpříkazu , jak je znázorněno v následujícím příkladu:

<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>

Optimalizace prostředků image

Obrázky jsou některé z nejdražších prostředků, které aplikace používají, a často se zaznamenávají ve vysokém rozlišení. I když to vytváří živé obrázky plné podrobností, aplikace, které tyto obrázky zobrazují, obvykle vyžadují větší využití procesoru k dekódování obrázku a více paměti pro uložení dekódovaného obrázku. Při zmenšení velikosti zobrazení je plýtvání dekódováním obrázku s vysokým rozlišením v paměti. Místo toho snižte využití procesoru a využití paměti vytvořením verzí uložených imagí, které jsou blízko předpovídané velikosti zobrazení. Například obrázek zobrazený v zobrazení seznamu by pravděpodobně měl mít nižší rozlišení než obrázek zobrazený na celé obrazovce.

Kromě toho by se obrázky měly vytvářet jenom v případě potřeby a měly by se uvolnit, jakmile je aplikace už nevyžaduje. Pokud například aplikace zobrazuje obrázek čtením dat z datového proudu, ujistěte se, že se stream vytvoří jenom v případě, že je to nutné, a ujistěte se, že se stream uvolní, když už není potřeba. Toho lze dosáhnout vytvořením datového proudu při vytvoření stránky nebo při Page.Appearing spuštění události a následným zrušením streamu Page.Disappearing při spuštění události.

Při stahování obrázku pro zobrazení pomocí ImageSource.FromUri(Uri) metody se ujistěte, že se stažený obrázek ukládá do mezipaměti po určitou dobu. Další informace najdete v tématu Ukládání obrázků do mezipaměti.

Zmenšení velikosti vizuálního stromu

Zmenšení počtu prvků na stránce urychlí vykreslení stránky. Existují dvě hlavní techniky, jak toho dosáhnout. Prvním je skrýt prvky, které nejsou viditelné. Vlastnost IsVisible každého prvku určuje, zda prvek má být součástí vizuálního stromu, nebo ne. Proto pokud prvek není viditelný, protože je skrytý za jinými prvky, buď odeberte prvek nebo nastavte jeho IsVisible vlastnost na false.

Druhou technikou je odebrání nepotřebných prvků. Například následující příklad ukazuje rozložení stránky obsahující více Label prvků:

<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>

Stejné rozložení stránky lze udržovat s nižším počtem prvků, jak je znázorněno v následujícím příkladu:

<VerticalStackLayout Padding="20,35,20,20"
                     Spacing="25">
    <Label Text="Hello" />
    <Label Text="Welcome to the App!" />
    <Label Text="Downloading Data..." />
</VerticalStackLayout>

Zmenšení velikosti slovníku prostředků aplikace

Všechny prostředky, které se používají v celé aplikaci, by se měly ukládat do slovníku prostředků aplikace, aby nedocházelo k duplikování. To vám pomůže snížit množství XAML, které je potřeba analyzovat v celé aplikaci. Následující příklad ukazuje HeadingLabelStyle prostředek, který se používá v celé aplikaci, a tak je definován ve slovníku prostředků aplikace:

<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>

Kód XAML, který je specifický pro stránku, by ale neměl být zahrnut do slovníku prostředků aplikace, protože prostředky se pak budou analyzovat při spuštění aplikace místo toho, aby je vyžadovala stránka. Pokud prostředek používá stránka, která není úvodní stránkou, měla by být umístěna ve slovníku prostředků pro tuto stránku, a proto pomáhá snížit analyzovaný KÓD XAML při spuštění aplikace. Následující příklad ukazuje HeadingLabelStyle prostředek, který je pouze na jedné stránce, a tak je definován ve slovníku prostředků stránky:

<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>

Další informace o prostředcích aplikace naleznete v tématu Styl aplikace využívající XAML.

Zmenšení velikosti aplikace

Když .NET MAUI sestaví vaši aplikaci, můžete pomocí linkeru s názvem ILLink zmenšit celkovou velikost aplikace. ILLink zmenšuje velikost analýzou zprostředkujícího kódu vytvořeného kompilátorem. Odebere nepoužívané metody, vlastnosti, pole, události, struktury a třídy a vytvoří aplikaci, která obsahuje pouze závislosti kódu a sestavení, které jsou nezbytné ke spuštění aplikace.

Další informace o konfiguraci chování linkeru naleznete v tématu Propojení aplikace pro Android, Propojení aplikace pro iOS a Propojení aplikace Mac Catalyst.

Zkrácení doby aktivace aplikace

Všechny aplikace mají dobu aktivace, což je doba mezi spuštěním aplikace a okamžikem, kdy je aplikace připravená k použití. Toto období aktivace poskytuje uživatelům první dojem z aplikace, takže je důležité zkrátit dobu aktivace a vnímání uživatele, aby získali příznivé první dojem z aplikace.

Než aplikace zobrazí počáteční uživatelské rozhraní, měla by poskytnout úvodní obrazovku, která uživateli oznámí, že se aplikace spouští. Pokud aplikace nemůže rychle zobrazit své počáteční uživatelské rozhraní, úvodní obrazovka by měla být použita k informování uživatele o průběhu v průběhu období aktivace, aby nabídla jistotu, že aplikace nebyla zablokovaná. Toto zajištění může být indikátor průběhu nebo podobný ovládací prvek.

Během období aktivace aplikace spouštějí logiku aktivace, která často zahrnuje načítání a zpracování prostředků. Dobu aktivace je možné snížit tím, že se zajistí, aby se požadované prostředky zabalily do aplikace, a ne vzdáleně načítaly. Například za určitých okolností může být vhodné během období aktivace načíst místně uložená zástupná data. Jakmile se zobrazí počáteční uživatelské rozhraní a uživatel bude moct s aplikací pracovat, zástupná data se dají postupně nahradit ze vzdáleného zdroje. Kromě toho by logika aktivace aplikace měla provádět jenom práci, která je nutná k tomu, aby uživatel mohl aplikaci začít používat. To může pomoct v případě, že zpožďuje načítání dalších sestavení, protože se sestavení načtou při prvním použití.

Pečlivě zvolte kontejner injektáže závislostí.

Kontejnery injektáže závislostí přinášejí do mobilních aplikací další omezení výkonu. Registrace a řešení typů v kontejneru má náklady na výkon kvůli použití reflexe kontejneru při vytváření jednotlivých typů, zejména pokud jsou závislosti rekonstruovány pro každou navigaci na stránce v aplikaci. Pokud existuje mnoho nebo hlubokých závislostí, mohou se náklady na vytvoření výrazně zvýšit. Kromě toho může registrace typu, ke které obvykle dochází při spuštění aplikace, mít výrazný dopad na dobu spuštění, která závisí na použitém kontejneru. Další informace o injektáži závislostí v aplikacích .NET MAUI najdete v tématu Injektáž závislostí.

Jako alternativu může být injektáž závislostí výkonnější implementací ručně pomocí továren.

Vytváření aplikací shellu

Aplikace .NET MAUI Shell poskytují názorné navigační prostředí založené na informačních seznamech a kartách. Pokud je možné uživatelské prostředí vaší aplikace implementovat s prostředím Shell, je užitečné to udělat. Aplikace prostředí pomáhají vyhnout se špatnému spouštěcímu prostředí, protože stránky se vytvářejí na vyžádání v reakci na navigaci, nikoli při spuštění aplikace, což se vyskytuje u aplikací, které používají TabbedPage. Další informace najdete v tématu Přehled prostředí.

Optimalizace výkonu ListView

Při použití ListViewexistuje řada uživatelských prostředí, která by se měla optimalizovat:

  • Inicializace – časový interval začínající při vytvoření ovládacího prvku a ukončení, kdy se položky zobrazují na obrazovce.
  • Posouvání – možnost procházet seznamem a zajistit, aby uživatelské rozhraní nezpožďuje za dotykovými gesty.
  • Interakce s přidáváním, odstraňováním a výběrem položek

Ovládací ListView prvek vyžaduje, aby aplikace dodá data a šablony buněk. Jak toho dosáhnete, bude mít velký dopad na výkon ovládacího prvku. Další informace najdete v tématu Mezipaměť dat.

Použití asynchronního programování

Celkovou odezvu aplikace je možné zvýšit a kritické body výkonu se často vyhýbají pomocí asynchronního programování. V .NET je vzor návrhu pro asynchronní operace založený na úlohách (TAP) doporučeným vzorem návrhu. Nesprávné použití TAP ale může vést k nevýkonným aplikacím.

Základy

Při používání tap by se měly dodržovat následující obecné pokyny:

  • Seznamte se s životním cyklem úlohy, který je reprezentován výčtem TaskStatus . Další informace naleznete v tématu Význam Stavu úkolů a Stav úkolu.
  • Task.WhenAll Pomocí metody asynchronně počkejte na dokončení více asynchronních operací, nikoli jednotlivě await na řadu asynchronních operací. Další informace naleznete v tématu Task.WhenAll.
  • Task.WhenAny Pomocí metody asynchronně počkejte na dokončení jedné z několika asynchronních operací. Další informace naleznete v tématu Task.WhenAny.
  • Použijte metodu Task.Delay k vytvoření objektu Task , který se dokončí po zadaném čase. To je užitečné pro scénáře, jako je dotazování na data a zpoždění zpracování uživatelského vstupu pro předem určený čas. Další informace naleznete v tématu Task.Delay.
  • Pomocí metody proveďte náročné synchronní operace procesoru Task.Run ve fondu vláken. Tato metoda je zkratkou pro metodu TaskFactory.StartNew s nejoptimálnější sadou argumentů. Další informace naleznete v tématu Task.Run.
  • Nepokoušejte se vytvářet asynchronní konstruktory. Místo toho použijte k správné await inicializaci události životního cyklu nebo samostatnou logiku inicializace. Další informace naleznete v tématu Asynchronní konstruktory na blog.stephencleary.com.
  • Pomocí opožděného vzoru úlohy se vyhněte čekání na dokončení asynchronních operací během spouštění aplikace. Další informace naleznete v tématu AsyncLazy.
  • Vytvořte obálku úloh pro existující asynchronní operace, které nepoužívají TAP, vytvořením TaskCompletionSource<T> objektů. Tyto objekty získávají výhody Task programovatelnosti a umožňují řídit životnost a dokončení přidruženého Task. Další informace naleznete v tématu Příroda TaskCompletionSource.
  • Vrácení objektu Task namísto vrácení očekávaného Task objektu, pokud není potřeba zpracovat výsledek asynchronní operace. To je výkonnější kvůli méně prováděnému přepínání kontextu.
  • Knihovnu toku dat TPL (Task Parallel Library) použijte ve scénářích, jako je zpracování dat, jakmile jsou k dispozici, nebo pokud máte více operací, které musí vzájemně komunikovat asynchronně. Další informace najdete v tématu Tok dat (paralelní knihovna úloh).

Uživatelské rozhraní

Při použití TAP s ovládacími prvky uživatelského rozhraní by se měly dodržovat následující pokyny:

  • Pokud je k dispozici, zavolejte asynchronní verzi rozhraní API. Tím se odblokuje vlákno uživatelského rozhraní, které pomůže zlepšit uživatelské prostředí s aplikací.

  • Aktualizujte prvky uživatelského rozhraní daty z asynchronních operací ve vlákně uživatelského rozhraní, abyste se vyhnuli vyvolání výjimek. Aktualizace ListView.ItemsSource vlastnosti se ale automaticky zařadí do vlákna uživatelského rozhraní. Informace o určení, jestli je kód spuštěný ve vlákně uživatelského rozhraní, naleznete v tématu Vytvoření vlákna ve vlákně uživatelského rozhraní.

    Důležité

    Všechny vlastnosti ovládacího prvku, které se aktualizují prostřednictvím datové vazby, se automaticky zařadí do vlákna uživatelského rozhraní.

Zpracování chyb

Při používání tap by se měly dodržovat následující pokyny pro zpracování chyb:

  • Seznamte se s asynchronním zpracováním výjimek. Neošetřené výjimky vyvolané kódem, který běží asynchronně, se šíří zpět do volajícího vlákna s výjimkou určitých scénářů. Další informace najdete v tématu Zpracování výjimek (paralelní knihovna úloh).
  • Vyhněte se vytváření async void metod a místo toho vytvořte async Task metody. Ty umožňují snadnější zpracování chyb, kompozičnost a testovatelnost. Výjimkou tohoto návodu jsou asynchronní obslužné rutiny událostí, které musí vrátit void. Další informace najdete v tématu Vyhněte se asynchronnímu Voidu.
  • Nekombinujte blokující a asynchronní kód voláním Task.WaitTask.Result, nebo GetAwaiter().GetResult metod, protože mohou vést k zablokování. Pokud však musí být toto vodítko porušeno, upřednostňovaným přístupem je volat metodu GetAwaiter().GetResult , protože zachovává výjimky úkolu. Další informace najdete v tématu Asynchronní zpracování výjimek a zpracování výjimek úloh v .NET 4.5.
  • Kdykoli je to možné, použijte metodu ConfigureAwait k vytvoření kódu bez kontextu. Kontextový kód má lepší výkon pro mobilní aplikace a je užitečnou technikou pro zabránění vzájemnému zablokování při práci s částečně asynchronním základem kódu. Další informace naleznete v tématu Konfigurace kontextu.
  • Úlohy pokračování použijte pro funkce, jako je zpracování výjimek vyvolaných předchozí asynchronní operací, a zrušení pokračování před jeho spuštěním nebo během jeho spuštění. Další informace naleznete v tématu Řetězení úkolů pomocí průběžné úlohy.
  • Použijte asynchronní ICommand implementaci při vyvolání asynchronních operací z objektu ICommand. Tím se zajistí, že všechny výjimky v asynchronní logice příkazů je možné zpracovat. Další informace najdete v tématu Asynchronní programování: Vzory pro asynchronní aplikace MVVM: Příkazy.

Zpoždění nákladů na vytváření objektů

Opožděné inicializace lze použít k odložit vytvoření objektu, dokud se poprvé nepoužije. Tato technika se primárně používá ke zlepšení výkonu, zabránění výpočtům a snížení požadavků na paměť.

Zvažte použití opožděné inicializace pro objekty, které jsou nákladné k vytvoření v následujících scénářích:

  • Aplikace nemusí objekt používat.
  • Před vytvořením objektu se musí dokončit další nákladné operace.

Třída Lazy<T> se používá k definování opožděného inicializovaného typu, jak je znázorněno v následujícím příkladu:

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)
{
    ...
}

Opožděné inicializace nastane při prvním přístupu k Lazy<T>.Value vlastnosti. Zabalený typ se vytvoří a vrátí při prvním přístupu a uloží se pro jakýkoli budoucí přístup.

Další informace o opožděné inicializaci naleznete v tématu Opožděná inicializace.

Prostředky IDisposable vydané verze

Rozhraní IDisposable poskytuje mechanismus pro uvolnění prostředků. Poskytuje metodu Dispose , která by se měla implementovat pro explicitní uvolnění prostředků. IDisposable není destruktor a měl by být implementován pouze za následujících okolností:

  • Když třída vlastní nespravované prostředky. Typické nespravované prostředky, které vyžadují uvolnění souborů, datových proudů a síťových připojení.
  • Když třída vlastní spravované IDisposable prostředky.

Příjemci typu pak mohou volat implementaci IDisposable.Dispose k volným prostředkům, pokud už instance není nutná. Existují dva přístupy k dosažení tohoto:

  • Zabalením objektu IDisposableusing do příkazu.
  • Zabalením volání IDisposable.Dispose do try/finally bloku.

Zabalení objektu IDisposable v příkazu using

Následující příklad ukazuje, jak zalomit IDisposable objekt v using příkazu:

public void ReadText(string filename)
{
    string text;
    using (StreamReader reader = new StreamReader(filename))
    {
        text = reader.ReadToEnd();
    }
    ...
}

StreamReader Třída implementuje IDisposablea using příkaz poskytuje pohodlnou syntaxi, která volá metodu StreamReader.DisposeStreamReader objektu před tím, než přejde mimo rozsah. using V rámci bloku StreamReader je objekt jen pro čtení a nelze ho znovu přiřadit. Příkaz using také zajišťuje, že metoda je volána i v případě, že Dispose dojde k výjimce, protože kompilátor implementuje zprostředkující jazyk (IL) pro try/finally blok.

Zabalení volání IDisposable.Dispose v bloku try/finally

Následující příklad ukazuje, jak zabalit volání IDisposable.Dispose do try/finally bloku:

public void ReadText(string filename)
{
    string text;
    StreamReader reader = null;
    try
    {
        reader = new StreamReader(filename);
        text = reader.ReadToEnd();
    }
    finally
    {
        if (reader != null)
            reader.Dispose();
    }
    ...
}

Třída StreamReader implementuje IDisposablea finally blok volá metodu StreamReader.Dispose k uvolnění prostředku. Další informace naleznete v tématu IDisposable Interface.

Odhlášení odběru událostí

Aby se zabránilo nevracení paměti, události by se měly odhlásit před odstraněním objektu odběratele. Dokud se událost neodhlásí, delegát události v objektu publikování má odkaz na delegáta, který zapouzdřuje obslužnou rutinu události odběratele. Pokud objekt publikování obsahuje tento odkaz, uvolňování paměti paměti odběratele neuvolní.

Následující příklad ukazuje, jak se odhlásit od události:

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;
    }
}

Třída Subscriber se odhlásí od události ve své Dispose metodě.

K referenčním cyklům může také dojít při použití obslužných rutin událostí a syntaxe lambda, protože výrazy lambda mohou odkazovat na objekty a udržovat je naživu. Proto lze odkaz na anonymní metodu uložit do pole a použít k odhlášení odběru události, jak je znázorněno v následujícím příkladu:

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;
    }
}

Pole _handler udržuje odkaz na anonymní metodu a používá se k odběru událostí a odhlášení odběru.

Vyhněte se silným cyklům odkazů na iOS a Mac Catalyst

V některých situacích je možné vytvořit silné referenční cykly, které by mohly zabránit objektům v uvolnění paměti uvolňováním paměti. Představte si například případ, kdy NSObjectje do odvozeného kontejneru přidána NSObjectpodtřída -odvozená, například třída, která dědí z UIView, a je silně odkazována z Objective-C, jak je znázorněno v následujícím příkladu:

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));

Když tento kód vytvoří Container instanci, objekt jazyka C# bude mít silný odkaz na Objective-C objekt. MyView Podobně instance bude mít také silný odkaz na Objective-C objekt.

Kromě toho volání container.AddSubview zvýší počet odkazů na nespravovanou MyView instanci. V takovém případě vytvoří modul runtime GCHandle .NET pro iOS instanci pro zachování objektu MyView ve spravovaném kódu naživu, protože neexistuje žádná záruka, že všechny spravované objekty na něj budou odkazovat. Z pohledu MyView spravovaného kódu by byl objekt uvolněn po AddSubview(UIView) volání nebyl pro GCHandleobjekt .

Nespravovaný MyView objekt bude mít GCHandle odkazující na spravovaný objekt, který se označuje jako silné propojení. Spravovaný objekt bude obsahovat odkaz na Container instanci. Instance bude mít Container spravovaný odkaz na MyView objekt.

Za okolností, kdy obsažený objekt uchovává odkaz na svůj kontejner, je k dispozici několik možností pro řešení cyklických odkazů:

  • Vyhněte se cyklický odkaz tím, že zachováte slabý odkaz na kontejner.
  • Volání Dispose objektů
  • Ručně přerušte cyklus nastavením odkazu na kontejner na null.
  • Ručně odeberte obsažený objekt z kontejneru.

Použití slabých odkazů

Jedním ze způsobů, jak zabránit cyklu, je použít slabý odkaz z podřízeného objektu na nadřazený objekt. Výše uvedený kód může být například uvedený v následujícím příkladu:

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));

V tomto případě nebude objekt s obsahem udržovat nadřazený objekt naživu. Nadřazený však udržuje dítě naživu prostřednictvím volání container.AddSubView.

K tomu dochází také v rozhraních API pro iOS, která používají model delegáta nebo zdroje dat, kde třída partnerského vztahu obsahuje implementaci. Například při nastavování Delegate vlastnosti nebo DataSource třídy UITableView .

V případě tříd, které jsou vytvořeny čistě pro účely implementace protokolu, například IUITableViewDataSource, co můžete udělat místo vytvoření podtřídy, můžete pouze implementovat rozhraní ve třídě a přepsat metodu a přiřadit DataSource vlastnost .this

Odstranění objektů se silnými odkazy

Pokud existuje silný odkaz a závislost je obtížné odebrat, vymažte metodu Dispose nadřazeného ukazatele.

U kontejnerů přepište metodu Dispose pro odebrání obsažených objektů, jak je znázorněno v následujícím příkladu:

class MyContainer : UIView
{
    public override void Dispose()
    {
        // Brute force, remove everything
        foreach (var view in Subviews)
        {
              view.RemoveFromSuperview();
        }
        base.Dispose();
    }
}

U podřízeného objektu, který zachovává silný odkaz na nadřazený objekt, vymažte odkaz na nadřazený objekt v implementaci Dispose :

class MyChild : UIView
{
    MyContainer _container;

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

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