Výkon Xamarin.iOS

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. Optimalizace výkonu ale zahrnuje nejen implementaci efektivního kódu. Je také potřeba zvážit zkušenosti uživatele 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í.

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 výkon pro různé platformy, který popisuje jiné než platformy specifické techniky ke zlepšení využití paměti a výkonu aplikací vytvořených pomocí platformy Xamarin.

Vyhněte se silným cyklům odkazů

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 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ří 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. Když k tomu dojde, modul runtime Xamarin.iOS vytvoří GCHandle 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 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ů:

  • Ručně přerušte cyklus nastavením odkazu na kontejner na null.
  • Ručně odeberte obsažený objekt z kontejneru.
  • Volání Dispose objektů
  • Vyhněte se cyklický odkaz, který udržuje 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 objektu na nadřazený objekt. Například výše uvedený kód by mohl být napsán takto:

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

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

K tomu dochází také v rozhraních API pro iOS, která používají vzor delegáta nebo zdroje dat, kde partnerský vztah obsahuje implementaci; Například při nastavování Delegate vlastnost nebo DataSourceUITableView ve třídě.

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

Slabý atribut

Xamarin.iOS 11.10 zavedl [Weak] atribut. Podobně jako WeakReference <T>, [Weak] lze použít k přerušení silných cyklických odkazů, ale s ještě méně 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í použití [Weak] kódu 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í [Weak] v kontextu modelu delegování :

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

Odstraňová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 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ý 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)
    {
        this.container = container;
    }
    public override void Dispose ()
    {
        container = null;
    }
}

Další informace o vydávání silných odkazů naleznete v tématu Release IDisposable Resources. Tady jsou také další informace o uvolňování paměti.

Více informací

Další informace naleznete v tématu Pravidla pro zabránění zachování cyklů na Cocoa With Love, Je to chyba v MonoTouch GC na StackOverflow a Proč nemůže MonoTouch GC zabít spravované objekty s refcount > 1 na StackOverflow.

Optimalizace zobrazení tabulek

Uživatelé očekávají hladké posouvání a rychlé načítání UITableView instancí. Výkon posouvání však může mít za to, že 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 k zabránění nízkému UITableView výkonu:

  • Opakovaně používejte buňky. Další informace naleznete v tématu Opakované použití buněk.
  • Snižte počet dílčích zobrazení.
  • Obsah buňky, který se načte z webové služby, ukládá do mezipaměti.
  • Pokud nejsou identické, ukažte výšku všech řádků do mezipaměti.
  • Udělejte buňku a všechna ostatní zobrazení neprůsadná.
  • Vyhněte se škálování a přechodům obrázků.

Tyto techniky společně můžou pomoct zajistit UITableView bezproblémové posouvání instancí.

Opakované použití buněk

Při zobrazení stovek řádků v objektu UITableViewby to bylo plýtvání pamětí k vytvoření stovek UITableViewCell objektů, když se na obrazovce najednou zobrazí jen malý počet. Místo toho lze do paměti načíst pouze buňky viditelné na obrazovce, přičemž obsah načtený do těchto opakovaně využitých buněk. Tím se zabrání vytvoření instance stovek dalších objektů, což šetří čas a paměť.

Proto když buňka zmizí z obrazovky, její zobrazení lze umístit 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;
    }
}

Při posouvání uživatel volá GetCell přepsání, UITableView aby se zobrazila nová zobrazení. Toto přepsání pak volá metodu DequeueReusableCell a pokud je buňka k dispozici pro opakované použití, bude vrácena.

Další informace najdete v tématu Opětovné použití buňky při naplnění tabulky daty.

Použití neprůhlého zobrazení

Ujistěte se, že všechna zobrazení, která nemají definovanou průhlednost, mají nastavenou vlastnost Opaque . Tím zajistíte optimální vykreslení zobrazení systémem výkresu. To je zvlášť důležité, když je zobrazení vložené do nebo UIScrollViewje součástí složité animace. V opačném případě systém výkresu složeného zobrazení s jiným obsahem, což může výrazně ovlivnit výkon.

Vyhněte se obsahem tuku XIB

I když xib byly do značné míry nahrazeny scénáři, existují určité okolnosti, kdy xib může být stále používán. Při načtení XIB do paměti se veškerý jeho obsah načte do paměti včetně všech imagí. Pokud XIB obsahuje zobrazení, které se okamžitě nepoužívá, dochází k plýtvání pamětí. Při použití XIB proto zajistěte, aby byl na kontroler zobrazení pouze jeden XIB, a pokud je to možné, oddělte hierarchii zobrazení kontroleru zobrazení do samostatných XIB.

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í. Proto při zobrazení obrázku ze sady aplikace v UIImageViewsouboru zajistěte, aby byl obrázek stejně UIImageView velký. Škálování imagí za běhu může být nákladná operace, zejména pokud UIImageView je vložena do objektu UIScrollView.

Další informace najdete v tématu Optimalizace prostředků image v průvodci výkonem napříč platformami.

Testování na zařízeních

Začněte nasazovat a testovat aplikaci na fyzickém zařízení co nejdříve. Simulátory neodpovídají chování a omezením zařízení a proto je důležité co nejdříve testovat ve scénáři skutečného zařízení.

Konkrétně simulátor nijak nenapodobuje omezení paměti nebo procesoru fyzického zařízení.

Synchronizace animací s aktualizací zobrazení

Hry mají tendenci mít těsné smyčky ke spuštění herní logiky a aktualizaci obrazovky. Typická frekvence snímků se pohybuje od třiceti do 60 snímků za sekundu. Někteří vývojáři se cítí, že by měli aktualizovat obrazovku co nejvícekrát za sekundu, zkombinují svou herní simulaci s aktualizacemi obrazovky a mohou být lákavé jít nad rámec 6 snímků za sekundu.

Server zobrazení ale provádí aktualizace obrazovky v maximálním limitu 60krát za sekundu. Proto pokus o aktualizaci obrazovky rychleji, než je tento limit, může vést k roztržení obrazovky a mikro-koktání. Nejlepší je strukturovat kód tak, aby se aktualizace obrazovky synchronizovaly s aktualizací zobrazení. Toho lze dosáhnout pomocí CoreAnimation.CADisplayLink třídy, což je časovač vhodný pro vizualizaci a hry, které běží na šedesáti rámcích za sekundu.

Vyhněte se průhlednosti základních animací

Vyhýbejte se základním animačním průhlednosti, aby se zlepšil výkon při vytváření rastrových obrázků. Obecně platí, že pokud je to možné, vyhněte se průhledným vrstvám a rozmazaným ohraničení.

Vyhněte se generování kódu

Dynamické generování kódu pomocí System.Reflection.Emit modulu runtime dynamického jazyka nebo modulu runtime dynamického jazyka se musí vyhnout, protože jádro iOS brání dynamickému spuštění kódu.

Shrnutí

Tento článek popisuje a popisuje techniky zvýšení výkonu aplikací vytvořených pomocí Xamarin.iOS. Souhrnně tyto techniky mohou výrazně snížit množství práce prováděné procesorem a množství paměti spotřebované aplikací.