Výkon Xamarin.iOS
Nízký výkon aplikace se projevuje mnoha způsoby. Může se zdát, že aplikace nereaguje, může způsobit pomalé posouvání a snížit životnost baterie. Optimalizace výkonu ale zahrnuje více než jen implementaci efektivního kódu. Je také třeba zvážit zkušenosti uživatele s výkonem aplikace. Například zajištění, že se operace provádějí bez blokování, aby uživatel mohl provádět jiné aktivity, může pomoct zlepšit uživatelské prostředí.
Tento dokument popisuje techniky, které lze použít ke zlepšení výkonu a využití paměti v aplikacích Xamarin.iOS.
Poznámka
Než si přečtete tento článek, měli byste si nejprve přečíst článek Výkonnapříč platformami, který popisuje jiné než platformové techniky pro zlepšení využití paměti a výkonu aplikací sestavených pomocí platformy Xamarin.
Vyhněte se silným kruhovým odkazům
V některých situacích je možné vytvořit silné referenční cykly, které by mohly objektům zabránit v uvolnění paměti systémem uvolňování paměti. Představte si například případ, kdy je do kontejneru odvozeného typu přidána podtřída odvozená z , například třída, která dědí z , a je silně odkazována z třídy , jak je znázorněno v následujícím příkladu NSObjectUIViewNSObjectObjective-C kódu:
class Container : UIView
{
public void Poke ()
{
// Call this method to poke this object
}
}
class MyView : UIView
{
Container parent;
public MyView (Container parent)
{
this.parent = parent;
}
void PokeParent ()
{
parent.Poke ();
}
}
var container = new Container ();
container.AddSubview (new MyView (container));
Když tento kód vytvoří instanci, bude mít objekt Container jazyka C# silný odkaz na Objective-C objekt. Podobně instance MyView bude mít také silný odkaz na Objective-C objekt.
Kromě toho volání zvýší container.AddSubview počet odkazů na nespravovanou MyView instanci. Když k tomu dojde, vytvoří modul runtime Xamarin.iOS instanci, která zajistí, aby byl objekt ve spravovaném kódu v provozu, protože neexistuje žádná záruka, že na něj budou všechny spravované objekty GCHandleMyView odkazovat. Z pohledu spravovaného kódu by byl objekt uvolněn po volání, kdyby MyViewAddSubview nebyl pro GCHandle .
Nespravovaný MyView objekt bude GCHandle odkazovat na spravovaný objekt, který se označuje jako MyView. Spravovaný objekt bude obsahovat odkaz na Container instanci . Instance pak Container bude mít spravovaný odkaz na objekt MyView .
V případech, kdy obsažený objekt uchovává propojení se svým kontejnerem, je k dispozici několik možností pro řešení cyklického odkazu:
- Cyklus ručně přerušte nastavením odkazu na kontejner na
null. - Ručně odeberte objekt obsažený v kontejneru.
- Volejte
Disposeu objektů . - Vyhněte se cyklické referenci a udržujte slabý odkaz na kontejner. Další informace o slabých odkazech.
Použití WeakReferences
Jedním ze způsob, jak zabránit cyklu, je použít slabý odkaz z podřízeného do nadřazeného objektu. Výše uvedený kód by například mohl být napsán následujícím kódem:
class Container : UIView
{
public void Poke ()
{
// Call this method to poke this object
}
}
class MyView : UIView
{
WeakReference<Container> weakParent;
public MyView (Container parent)
{
this.weakParent = new WeakReference<Container> (parent);
}
void PokeParent ()
{
if (weakParent.TryGetTarget (out var parent))
parent.Poke ();
}
}
var container = new Container ();
container.AddSubview (new MyView (container));
Zde obsažený objekt nezachová nadřazený objekt. Nadřazená třída však udržuje podřízené položky při živém připojení prostřednictvím volání metody container.AddSubView .
K tomu dochází také v rozhraních API pro iOS, která používají vzor delegáta nebo zdroje dat, kde partnerská třída obsahuje implementaci. například při nastavováníDelegate neboDataSource ve UITableView třídě .
V případě tříd, které jsou vytvořeny čistě pro implementaci protokolu, například , můžete místo vytváření podtřídy implementovat rozhraní ve třídě a přepsat metodu a přiřadit vlastnost k IUITableViewDataSourceDataSourcethis .
Slabý atribut
Xamarin.iOS 11.10 představil atribut . Podobně WeakReference <T> jako lze použít k přerušení [Weak] silných WeakReference <T>, ale s ještě menším kódem.
Vezměte v úvahu následující kód, který používá WeakReference <T> :
public class MyFooDelegate : FooDelegate {
WeakReference<MyViewController> controller;
public MyFooDelegate (MyViewController ctrl) => controller = new WeakReference<MyViewController> (ctrl);
public void CallDoSomething ()
{
MyViewController ctrl;
if (controller.TryGetTarget (out ctrl)) {
ctrl.DoSomething ();
}
}
}
Ekvivalentní kód s použitím [Weak] je mnohem stručnější:
public class MyFooDelegate : FooDelegate {
[Weak] MyViewController controller;
public MyFooDelegate (MyViewController ctrl) => controller = ctrl;
public void CallDoSomething () => controller.DoSomething ();
}
Následuje další příklad použití v kontextu vzoru [Weak][Weak]
public class MyViewController : UIViewController
{
WKWebView webView;
protected MyViewController (IntPtr handle) : base (handle) { }
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
webView = new WKWebView (View.Bounds, new WKWebViewConfiguration ());
webView.UIDelegate = new UIDelegate (this);
View.AddSubview (webView);
}
}
public class UIDelegate : WKUIDelegate
{
[Weak] MyViewController controller;
public UIDelegate (MyViewController ctrl) => controller = ctrl;
public override void RunJavaScriptAlertPanel (WKWebView webView, string message, WKFrameInfo frame, Action completionHandler)
{
var msg = $"Hello from: {controller.Title}";
var alertController = UIAlertController.Create (null, msg, UIAlertControllerStyle.Alert);
alertController.AddAction (UIAlertAction.Create ("Ok", UIAlertActionStyle.Default, null));
controller.PresentViewController (alertController, true, null);
completionHandler ();
}
}
Likvidace objektů se silnými odkazy
Pokud existuje silný odkaz a je obtížné závislost odebrat, vytvořte metodu, která Dispose vymaže nadřazený ukazatel.
Pro kontejnery přepište Dispose metodu a odeberte obsažené objekty, jak je znázorněno v následujícím příkladu kódu:
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ý uchovává silný odkaz na nadřazený objekt, vymažte odkaz na nadřazený objekt v Dispose implementaci:
class MyChild : UIView
{
MyContainer container;
public MyChild (MyContainer container)
{
this.container = container;
}
public override void Dispose ()
{
container = null;
}
}
Další informace o vydávání silných odkazů najdete v tématu Release IDisposable Resources. V tomto blogovém příspěvku je také dobrá diskuze: Xamarin.iOS, systém uvolňování pamětia já .
Další informace
Další informace najdete v tématu Pravidla, která se mají vyhnout cyklům uchování u cocoa with love, Jedná se o chybu v MonoTouch GC na StackOverflow a Proč nemůže monoTouch GC kill managed objects with refcount 1? na webu StackOverflow.
Optimalizace zobrazení tabulek
Uživatelé očekávají u instancí plynulé posouvání a UITableView rychlé načítání. Výkon posouvání ale může utrpět, pokud buňky obsahují hluboko vnořené hierarchie zobrazení nebo když buňky obsahují složitá rozložení. Existují však techniky, které lze použít, abyste se vyhnuli špatnému UITableView výkonu:
- Opakovaně používat buňky. Další informace najdete v tématu Opětovné použití buněk.
- Snižte počet dílčích zobrazení.
- Obsah buňky načtený z webové služby můžete ukládat do mezipaměti.
- Pokud nejsou identické, můžete do mezipaměti ukládat výšku všech řádků.
- Načtou buňku a další zobrazení jako neprůhledná.
- Vyhněte se škálování obrázků a přechodům.
Tyto techniky společně pomáhají udržovat plynulé UITableView posouvání instancí.
Opakované použití buněk
Při zobrazení stovek řádků v objektu by bylo plýtvání pamětí při vytváření stovek objektů, pokud se na obrazovce zobrazí pouze malý UITableViewUITableViewCell počet objektů najednou. Místo toho lze do paměti načíst pouze buňky, které jsou na obrazovce viditelné, a obsah se načte do těchto opětovně využitých buněk. To brání vytváření instancí stovek dalších objektů a šetří čas a paměť.
Proto když buňka zmizí z obrazovky, její zobrazení může být umístěno do fronty pro opakované použití, jak je znázorněno v následujícím příkladu kódu:
class MyTableSource : UITableViewSource
{
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
// iOS will create a cell automatically if one isn't available in the reuse pool
var cell = (MyCell) tableView.DequeueReusableCell (MyCellId, indexPath);
// Perform required cell actions
return cell;
}
}
Když se uživatel posouváním, UITableView volá přepsání , aby si GetCell vyžádal zobrazení nová. Toto přepsání pak zavolá DequeueReusableCell metodu a pokud je buňka k dispozici pro opakované použití, vrátí se.
Další informace najdete v tématu Opětovné použití buněk při naplnění tabulky daty.
Použití neprůhledných zobrazení
Zajistěte, aby každá zobrazení, která nemají definovanou průhlednost, měla Opaque nastavenou vlastnost . Tím se zajistí optimální vykreslení zobrazení systémem kreslení. To je zvláště důležité, pokud je zobrazení vložené do objektu nebo je UIScrollView součástí složité animace. V opačném případě systém kreslení zobrazí zobrazení s jiným obsahem, což může znatelně ovlivnit výkon.
Vyhněte se souborům XIB pro fat
Přestože soubory XIB byly do značné míry nahrazeny scénáři, existují určité okolnosti, kdy je stále možné používat soubory XIB. Při načtení XIB do paměti se veškerý jeho obsah načte do paměti, včetně obrázků. Pokud xib obsahuje zobrazení, které se okamžitě nevyužívá, dochází k plýtvání pamětí. Proto při použití souborů XIB zajistěte, aby pro každý kontroler zobrazení byl pouze jeden XIB, a pokud je to možné, oddělte hierarchii zobrazení kontroleru zobrazení do samostatných souborů XIB.
Optimalizace prostředků obrázků
Image jsou některé z nejdražších prostředků, které aplikace používají, a často se zachycují ve vysokém rozlišení. Proto při zobrazení obrázku ze sady aplikace v souboru zajistěte stejnou velikost obrázku a UIImageViewUIImageView . Škálování imagí za běhu může být nákladná operace, zejména pokud je UIImageView vložených do UIScrollView objektu .
Další informace najdete v tématu Optimalizace prostředků obrázků vprůvodci výkonem pro více platforem.
Testování na zařízeních
Začněte nasazovat a testovat aplikaci na fyzickém zařízení co nejdříve. Simulátory dokonale neodpovídají chování a omezením zařízení, a proto je důležité provést testování ve scénáři zařízení z reálného světa co nejdříve.
Konkrétně simulátor žádným způsobem nesimuluje omezení paměti nebo procesoru fyzického zařízení.
Synchronizace animací s aktualizací zobrazení
Hry mají tendenci mít úzké smyčky, které spustí herní logiku a aktualizují obrazovku. Typická snímková frekvence se pohybuje od 30 do 60 snímků za sekundu. Někteří vývojáři mají pocit, že by měli obrazovku aktualizovat tolikrát, kolikrát je to možné za sekundu, kombinovat svou herní simulaci s aktualizacemi obrazovky a může být vlákána tak, aby přesahoval 60 snímků za sekundu.
Zobrazovací server ale provádí aktualizace obrazovky s horním limitem 60krát za sekundu. Proto pokus o aktualizaci obrazovky rychleji, než je tento limit, může vést k odtržení obrazovky a mikrovláknění. Je nejlepší strukturovat kód tak, aby se aktualizace obrazovky synchronizovala s aktualizací zobrazení. Toho lze dosáhnout pomocí třídy , což je časovač vhodný pro vizualizaci a hry, které běží na CoreAnimation.CADisplayLink 60 snímcích za sekundu.
Vyhněte se průhlednosti základní animace
Vyhnete se průhlednosti základní animace a zlepšíte výkon při kompozicích rastrových obrázků. Obecně se vyhněte transparentním vrstvám a rozmazaným ohraničením, pokud je to možné.
Vyhněte se generování kódu
Dynamické generování kódu pomocí nástroje System.Reflection.Emit nebo System.Reflection.Emit musí být zabráněno, protože jádro iOS brání spuštění dynamického kódu.
Souhrn
Tento článek popisuje a popisuje techniky pro zvýšení výkonu aplikací vytvořených pomocí Xamarin. iOS. Souhrnně tyto techniky můžou významně snížit množství práce prováděné CPU a množství paměti, které aplikace spotřebovává.