Zlepšení výkonu aplikace
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živatelskou zkušenost 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 je možné profilovat pomocí dotnet-trace
v systémech Android, 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žijte zkompilované vazby
Kompilované vazby zlepšují výkon datových vazeb v aplikacích .NET MAUI vyhodnocováním 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.
Snižte zbytečné vazby
Nepoužívejte vazby pro obsah, který lze snadno nastavit staticky. Není žádná výhoda v svazování dat, která nemusí být svázaná, 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 VerticalStackLayout s jedním potomkem:
<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 prvek VerticalStackLayout 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 rozložení Grid 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í Grid, 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>
Optimalizujte prostředky obrazu
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. Je plýtváním dekódovat obrázek s vysokým rozlišením v paměti, když se bude zmenšovat na menší velikost pro zobrazení. 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 spuštěním události Page.Appearing a následným zrušením spuštění datového proudu, když se událost Page.Disappearing aktivuje.
Při stahování obrázku pro zobrazení pomocí metody ImageSource.FromUri(Uri) se ujistěte, že se stažený obrázek ukládá do mezipaměti po určitou dobu. Další informace naleznete v tématu Ukládání obrázků do mezipaměti.
Zmenšení počtu prvků na stránce
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 má být prvek viditelný na obrazovce. Pokud prvek není viditelný, protože je skrytý za jinými prvky, odeberte prvek nebo nastavte jeho vlastnost IsVisible
na false
. Nastavení vlastnosti IsVisible
prvku na false
zachová prvek ve vizuálním stromu, ale vyloučí ho z výpočtů vykreslování a rozložení.
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 prostředek HeadingLabelStyle
, 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 stránka, která není úvodní stránkou, používá prostředek, měl by být umístěn do jejích slovníku prostředků, což pomáhá snížit množství analyzovaného kódu XAML při spuštění aplikace. Následující příklad ukazuje zdroj HeadingLabelStyle
, který je pouze na jedné stránce, a proto je definován ve slovníku zdrojů 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 najdete v tématu Styl aplikací pomocí XAML.
Zmenšení velikosti aplikace
Když .NET MAUI sestaví vaši aplikaci, je možné použít linker s názvem ILLink a snížit tak 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 iOSa Propojení aplikace Mac Catalyst.
Zkrácení doby aktivace aplikace
Všechny aplikace mají období aktivace, což je čas 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, a proto je důležité zkrátit dobu aktivace a způsob, jak ji uživatel vnímá, 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 uklidnění může mít podobu indikátoru průběhu nebo podobného ovládacího prvku.
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, pokud dochází ke zpoždění načítání dalších sestavení, protože se sestavení načtou při prvním použití.
Pečlivě zvolte IoC kontejner.
Kontejnery injektáže závislostí přinášejí do mobilních aplikací další výkonová omezení. 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 injekce závislostí výkonnější, pokud je ručně implementována pomocí továren.
Vytvořte Shell aplikace
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 shellu pomáhají vyhnout se špatnému zážitku při spuštění, protože stránky se vytvářejí na požádání jako reakce na navigaci, a ne při spuštění aplikace, k čemuž dochází u aplikací, které používají TabbedPage. Další informace najdete v tématu Shell – přehled.
Optimalizace výkonu ListView
Při použití ListViewexistuje řada uživatelských prostředí, která by měla být optimalizována:
- inicializace – časový interval, který začíná při vytváření ovládacího prvku a končí, kdy se položky zobrazují na obrazovce.
- Scrollování – možnost procházet seznam a zajistit, aby uživatelské rozhraní nezaostávalo za dotykovými gesty.
- Interakce pro přidávání, odstraňování a výběr položek.
Ovládací prvek ListView vyžaduje, aby aplikace dodála 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 Ukládání dat do mezipaměti.
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 rozhraní .NET je asynchronní vzor založený na úlohách (TAP) doporučen jako návrhový vzor pro asynchronní operace. 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 cyklem úkolu, který je reprezentován výčtem
TaskStatus
. Další informace naleznete v tématu Význam stavu úkolu a Stav úkolu. - Pomocí metody
Task.WhenAll
asynchronně počkejte na dokončení více asynchronních operací, nikoli jednotlivěawait
řadu asynchronních operací. Další informace viz Task.WhenAll. - Pomocí metody
Task.WhenAny
asynchronně počkejte na dokončení jedné z několika asynchronních operací. Další informace naleznete v tématu Task.WhenAny. - Pomocí metody
Task.Delay
vytvořte objektTask
, 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. - Proveďte náročné synchronní operace procesoru ve fondu vláken pomocí metody
Task.Run
. Tato metoda je zkratkou pro metoduTaskFactory.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 události životního cyklu nebo samostatnou logiku inicializace pro správnou
await
jakékoli inicializace. Další informace viz Asynchronní Konstrukce na blog.stephencleary.com. - Pomocí lí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 části AsyncLazy.
- Vytvořte obálku úlohy pro existující asynchronní operace, které nepoužívají TAP, vytvořením
TaskCompletionSource<T>
objektů. Tyto objekty získávají výhody programovatelnostiTask
a umožňují řídit jak životnost, tak dokončení přidruženéTask
. Další informace naleznete v tématu Povaha TaskCompletionSource. - Vrátí objekt
Task
místo vrácení očekávaného objektuTask
, 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 (Knihovna pro paralelní zpracování ú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 vlastnosti
ListView.ItemsSource
se však automaticky zařadí do vlákna uživatelského rozhraní. Informace o určení, zda kód běží 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 (Task Parallel Library).
- Vyhněte se vytváření
async void
metod a místo toho vytvořteasync 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í vracetvoid
. Pro více informací viz Vyhněte se asynchronnímu Voidu. - Nekombinujte blokující a asynchronní kód voláním metody
Task.Wait
,Task.Result
neboGetAwaiter().GetResult
, protože mohou způsobit zablokování. Pokud však musí být toto vodítko porušeno, upřednostňovaným přístupem je volat metoduGetAwaiter().GetResult
, protože zachovává výjimky úkolů. Další informace najdete v tématu Asynchronní zpracování a Zpracování výjimek úloh v prostředí .NET 4.5. - Kdykoli je to možné, použijte
ConfigureAwait
metodu k vytvoření kódu bez kontextu. Kontextově nezávislý kód má lepší výkon pro mobilní aplikace a představuje užitečnou techniku pro zabránění vzájemnému zablokování při práci s částečně asynchronní kódovou základnou. Pro více informací nahlédněte do Konfigurace kontextu. - Použijte pokračující úlohy pro úlohy, jako je zpracování výjimek, které byly vyvolány předchozí asynchronní operací, a zrušení pokračování před jeho spuštěním nebo během jeho provádění. Další informace naleznete v tématu řetězení úkolů pomocí průběžných úkolů.
- Použijte asynchronní ICommand implementaci při vyvolání asynchronních operací z 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.
Odložení nákladů na vytváření objektů
Línou inicializaci lze použít k odložení vytvoření objektu, dokud není poprvé použit. 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>
slouží k definování lineárně 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)
{
...
}
Lenivá inicializace nastane při prvním přístupu na vlastnost Lazy<T>.Value
. Zabalený typ se vytvoří a vrátí při prvním přístupu a je uložen pro pozdější použití.
Další informace o opožděné inicializaci naleznete v tématu Opožděná inicializace.
Uvolnit prostředky IDisposable
Rozhraní IDisposable
poskytuje mechanismus pro uvolnění prostředků. Poskytuje metodu Dispose
, která by měla být implementována k explicitnímu 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, jejichž uvolnění je vyžadováno, zahrnují soubory, datové proudy a síťová připojení.
- Když třída vlastní spravované
IDisposable
prostředky.
Uživatelé typu pak mohou volat implementaci IDisposable.Dispose
pro uvolnění prostředků, když instance již není potřeba. Existují dva přístupy k dosažení tohoto:
- Zabalením objektu
IDisposable
do příkazuusing
. - Zabalením volání na
IDisposable.Dispose
do blokutry
/finally
.
Zabalení objektu IDisposable v příkazu using
Následující příklad ukazuje, jak zalomit objekt IDisposable
v příkazu using
:
public void ReadText(string filename)
{
string text;
using (StreamReader reader = new StreamReader(filename))
{
text = reader.ReadToEnd();
}
...
}
Třída StreamReader
implementuje IDisposable
a příkaz using
poskytuje pohodlnou syntaxi, která volá metodu StreamReader.Dispose
objektu StreamReader
před tím, než přejde mimo rozsah. V using
bloku je objekt StreamReader
jen pro čtení a nelze ho znovu přiřadit. Příkaz using
také zajišťuje, aby Dispose
metoda byla volána i v případě, že dojde k výjimce, protože kompilátor implementuje zprostředkující jazyk (IL) pro try
/finally
blok.
Zabalte volání IDisposable.Dispose do bloku try/finally
Následující příklad ukazuje, jak zabalit volání IDisposable.Dispose
do bloku 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();
}
...
}
Třída StreamReader
implementuje IDisposable
a blok finally
volá metodu StreamReader.Dispose
k uvolnění prostředku. Další informace naleznete v tématu rozhraní IDisposable.
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. Dokud publikační objekt obsahuje tento odkaz, uvolňování paměti neobnoví paměť objektu odběratele.
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
odhlásí odběr události ve své metodě Dispose
.
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 cirkulárním odkazům na iOS a Mac Catalyst
V některých situacích je možné vytvořit silné referenční cykly, které mohou zabránit uvolnění paměti objekty sběračem odpadu. Představte si například případ, kdy NSObject-odvozená podtřída, například třída, která dědí z UIView, je přidána do NSObject
-odvozeného kontejneru 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ří instanci Container
, objekt jazyka C# bude mít silný odkaz na objekt Objective-C. Podobně instance MyView
bude mít také silný odkaz na objekt Objective-C.
Kromě toho volání container.AddSubview
zvýší počet odkazů na nespravovanou instanci MyView
. V takovém případě vytvoří modul runtime .NET pro iOS instanci GCHandle
, která zachová objekt MyView
ve spravovaném kódu naživu, protože neexistuje žádná záruka, že na něj budou všechny spravované objekty odkazovat. Z pohledu spravovaného kódu by byl objekt MyView
uvolněn po volání AddSubview(UIView), kdyby nebylo GCHandle
.
Nespravovaný objekt MyView
bude mít GCHandle
, který ukazuje na spravovaný objekt, známý jako silný odkaz . Spravovaný objekt bude obsahovat odkaz na instanci Container
. Instance Container
bude mít spravovaný odkaz na objekt MyView
.
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 cirkulárnímu odkazu tím, že budete používat slabý odkaz na kontejner.
- Volání
Dispose
na objektech. - 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ý objekt však udržuje potomka naživu prostřednictvím volání container.AddSubView
.
K tomu dochází také v rozhraních API pro iOS, která používají vzor delegáta nebo zdroje dat, kde třída protějšku obsahuje implementaci. Například při nastavení vlastnosti Delegate nebo DataSource ve třídě UITableView.
V případě tříd, které jsou vytvořeny čistě pro účely implementace protokolu, například IUITableViewDataSource, co můžete dě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, metoda Dispose
vymaže nadřazený ukazatel.
U kontejnerů přepište implementaci metody Dispose
pro odstranění obsažených objektů, jak je uvedeno 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;
}
}