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ýkon napříč 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, NSObjectUIViewNSObjectkdy je do kontejneru odvozeného Objective-Ctypu 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 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, bude mít objekt jazyka C# silný odkaz na Objective-C objekt. Podobně instance bude MyView 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 GCHandleMyView 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 odkazovat. Z pohledu spravovaného kódu by MyView byl objekt uvolněn po AddSubview volání, kdyby nebyl pro GCHandle.

Nespravovaný MyView objekt bude odkazovat GCHandle 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 MyView objekt .

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 Dispose u 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 třídě UITableView .

V případě tříd, které jsou vytvořeny čistě pro implementaci protokolu, IUITableViewDataSourcenapříklad , můžete místo vytváření podtřídy implementovat rozhraní ve třídě a přepsat metodu a DataSourcethispřiřadit vlastnost k .

Slabý atribut

Xamarin.iOS 11.10 představil atribut . Podobně WeakReference <T>jako lze [Weak] použít k přerušení 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 [Weak] vzoru [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, Dispose vytvořte metodu, která vymaže nadřazený ukazatel.

Pro kontejnery přepište metodu Dispose 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ěti a 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 rychlé UITableView 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ů UITableViewv objektu by UITableViewCell bylo plýtvání pamětí při vytváření stovek objektů, pokud se na obrazovce zobrazí pouze malý 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, volá UITableView přepsání GetCell , aby si vyžádal zobrazení nová. Toto přepsání pak zavolá metodu DequeueReusableCell 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ěkpři naplnění tabulky daty.

Použití neprůhledných zobrazení

Zajistěte, aby každá zobrazení, která nemají definovanou průhlednost, měla nastavenou Opaque vlastnost . Tím se zajistí optimální vykreslení zobrazení systémem kreslení. To je zvláště důležité, pokud je zobrazení vložené UIScrollViewdo objektu nebo je 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 UIImageView velikost obrázku a UIImageView. Škálování imagí za běhu může být nákladná operace, zejména pokud je UIImageView vložených do UIScrollViewobjektu .

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í CoreAnimation.CADisplayLink třídy , což je časovač vhodný pro vizualizaci a hry, které běží na 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ě platí, že pokud je to možné, vyhněte se transparentním vrstvám a rozmazaným ohraničením.

Vyhněte se generování kódu

Je nutné se vyhnout dynamickému System.Reflection.Emit generování kódu pomocí modulu System.Reflection.Emit , protože jádro iOS brání dynamickému spouštění kódu.

Souhrn

Tento článek popisuje a probíral techniky pro zvýšení výkonu aplikací sestavený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í.