Wydajność platformy Xamarin.iOS
Niska wydajność aplikacji przedstawia się na wiele sposobów. Może to sprawić, że aplikacja wydaje się nie odpowiadać, może powodować powolne przewijanie i może zmniejszyć żywotność baterii. Jednak optymalizacja wydajności wymaga więcej niż tylko zaimplementowania wydajnego kodu. Należy również rozważyć środowisko użytkownika dotyczące wydajności aplikacji. Na przykład zapewnienie, że operacje są wykonywane bez blokowania użytkownikowi wykonywania innych działań, mogą pomóc w ulepszaniu środowiska użytkownika.
W tym dokumencie opisano techniki, których można użyć do zwiększenia wydajności i użycia pamięci w aplikacjach platformy Xamarin.iOS.
Uwaga
Przed przeczytaniem tego artykułu należy najpierw przeczytać artykuł Wydajność międzyplatformowa, w którym omówiono techniki specyficzne dla platformy, aby poprawić użycie pamięci i wydajność aplikacji utworzonych przy użyciu platformy Xamarin.
Unikaj silnych odwołań okrągłych
W niektórych sytuacjach istnieje możliwość utworzenia silnych cykli odwołań, które mogą uniemożliwić obiektom odzyskanie pamięci przez moduł odśmiecenia pamięci. Rozważmy na przykład przypadek, w którym podklasa NSObject
pochodna, taka jak klasa dziedziczona z UIView
klasy , jest dodawana do kontenera pochodnego NSObject
i jest silnie przywoływana z Objective-Cklasy , jak pokazano w poniższym przykładzie kodu:
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));
Po utworzeniu Container
wystąpienia ten kod będzie miał silne odwołanie do Objective-C obiektu w języku C#. MyView
Podobnie wystąpienie będzie mieć silne odwołanie do Objective-C obiektu.
Ponadto wywołanie metody , aby container.AddSubview
zwiększyć liczbę odwołań w wystąpieniu niezarządzanych MyView
. W takim przypadku środowisko uruchomieniowe platformy Xamarin.iOS tworzy GCHandle
wystąpienie w celu zachowania MyView
aktywności obiektu w kodzie zarządzanym, ponieważ nie ma gwarancji, że żadne obiekty zarządzane będą do niego odwoływać się. Z perspektywy MyView
kodu zarządzanego obiekt zostanie odzyskany po AddSubview
wywołaniu, gdyby nie obiekt GCHandle
.
Niezarządzany MyView
obiekt będzie miał GCHandle
wskazanie obiektu zarządzanego , znanego jako silny link. Obiekt zarządzany będzie zawierać odwołanie do Container
wystąpienia. Z kolei Container
wystąpienie będzie mieć zarządzane odwołanie do MyView
obiektu.
W sytuacjach, gdy zawarty obiekt przechowuje łącze do kontenera, dostępnych jest kilka opcji obsługi odwołania cyklicznego:
- Ręczne przerwanie cyklu przez ustawienie linku do kontenera na
null
. - Ręcznie usuń zawarty obiekt z kontenera.
- Wywołaj metodę
Dispose
na obiektach. - Unikaj odwołania cyklicznego utrzymującego słabe odwołanie do kontenera. Aby uzyskać więcej informacji na temat słabych odwołań.
Używanie funkcji WeakReferences
Jednym ze sposobów zapobiegania cyklu jest użycie słabego odwołania od elementu podrzędnego do elementu nadrzędnego. Na przykład powyższy kod można napisać w następujący sposób:
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));
W tym miejscu zawarty obiekt nie będzie utrzymywać elementu nadrzędnego przy życiu. Jednak rodzic utrzymuje dziecko przy życiu za pośrednictwem połączenia wykonanego do container.AddSubView
.
Dzieje się tak również w interfejsach API systemu iOS korzystających ze wzorca delegata lub źródła danych, gdzie klasa równorzędna zawiera implementację; na przykład podczas ustawiania Delegate
właściwość lub właściwość DataSource
UITableView
w klasie .
W przypadku klas, które są tworzone wyłącznie ze względu na implementację protokołu, na przykład IUITableViewDataSource
, co można zrobić, to zamiast tworzyć podklasę, można po prostu zaimplementować interfejs w klasie i zastąpić metodę, a następnie przypisać DataSource
właściwość do this
klasy .
Słaby atrybut
Program Xamarin.iOS 11.10 wprowadził [Weak]
atrybut . Podobnie jak WeakReference <T>
, [Weak]
można użyć do przerywania silnych odwołań okrągłych, ale przy użyciu jeszcze mniejszego kodu.
Rozważmy następujący kod, który używa WeakReference <T>
polecenia :
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 ();
}
}
}
Użycie równoważnego kodu [Weak]
jest znacznie bardziej zwięzłe:
public class MyFooDelegate : FooDelegate {
[Weak] MyViewController controller;
public MyFooDelegate (MyViewController ctrl) => controller = ctrl;
public void CallDoSomething () => controller.DoSomething ();
}
Poniżej przedstawiono inny przykład użycia w [Weak]
kontekście wzorca delegowania:
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 ();
}
}
Usuwanie obiektów z silnymi odwołaniami
Jeśli istnieje silne odwołanie i trudno jest usunąć zależność, wyczyść metodę Dispose
wskaźnika nadrzędnego.
W przypadku kontenerów zastąpij metodę Dispose
, aby usunąć zawarte obiekty, jak pokazano w poniższym przykładzie kodu:
class MyContainer : UIView
{
public override void Dispose ()
{
// Brute force, remove everything
foreach (var view in Subviews)
{
view.RemoveFromSuperview ();
}
base.Dispose ();
}
}
W przypadku obiektu podrzędnego, który utrzymuje silne odwołanie do elementu nadrzędnego, wyczyść odwołanie do elementu nadrzędnego w implementacji Dispose
:
class MyChild : UIView
{
MyContainer container;
public MyChild (MyContainer container)
{
this.container = container;
}
public override void Dispose ()
{
container = null;
}
}
Aby uzyskać więcej informacji na temat wydawania silnych odwołań, zobacz Release IDisposable Resources (Zwalnianie zasobów IDisposable). W tym miejscu znajduje się również więcej informacji dotyczących odzyskiwania pamięci.
Więcej informacji
Aby uzyskać więcej informacji, zobacz Rules to Avoid Retain Cycles on Cocoa With Love( Czy jest to usterka w usłudze MonoTouch GC w witrynie StackOverflow) i Why can't MonoTouch GC kill managed objects with refcount 1? on StackOverflow (Dlaczego nie można zabić zarządzanych obiektów MonoTouch GC z refcount > 1? w witrynie StackOverflow).
Optymalizowanie widoków tabel
Użytkownicy oczekują bezproblemowego przewijania i szybkiego ładowania dla UITableView
wystąpień. Jednak wydajność przewijania może cierpieć, gdy komórki zawierają głęboko zagnieżdżone hierarchie widoku lub gdy komórki zawierają złożone układy. Istnieją jednak techniki, których można użyć, aby uniknąć niskiej UITableView
wydajności:
- Użyj ponownie komórek. Aby uzyskać więcej informacji, zobacz Ponowne używanie komórek.
- Zmniejsz liczbę widoków podrzędnych.
- Zawartość komórki pamięci podręcznej pobierana z usługi internetowej.
- Buforuj wysokość dowolnych wierszy, jeśli nie są identyczne.
- Utwórz komórkę i inne widoki nieprzezroczyste.
- Unikaj skalowania i gradientów obrazów.
Zbiorczo te techniki mogą pomóc w UITableView
bezproblemowym przewijaniu wystąpień.
Ponowne używanie komórek
Podczas wyświetlania setek wierszy w obiekcie UITableView
byłoby to strata pamięci do utworzenia setek UITableViewCell
obiektów, gdy tylko niewielka liczba z nich jest wyświetlana na ekranie jednocześnie. Zamiast tego tylko komórki widoczne na ekranie można załadować do pamięci, a zawartość jest ładowana do tych ponownie użytych komórek. Zapobiega to utworzeniu wystąpienia setek dodatkowych obiektów, oszczędzaniu czasu i pamięci.
W związku z tym, gdy komórka zniknie z ekranu, jego widok można umieścić w kolejce do ponownego użycia, jak pokazano w poniższym przykładzie kodu:
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;
}
}
Gdy użytkownik przewija, wywołuje GetCell
przesłonięcie, UITableView
aby zażądać wyświetlenia nowych widoków. To zastąpienie wywołuje metodę DequeueReusableCell
, a jeśli komórka jest dostępna do ponownego użycia, zostanie zwrócona.
Aby uzyskać więcej informacji, zobacz Ponowne użycie komórek w wypełnianiu tabeli przy użyciu danych.
Używanie nieprzezroczystych widoków
Upewnij się, że wszystkie widoki, które nie mają zdefiniowanej przezroczystości, mają swój Opaque
zestaw właściwości. Zapewni to optymalne renderowanie widoków przez system rysunku. Jest to szczególnie ważne, gdy widok jest osadzony w UIScrollView
obiekcie lub jest częścią złożonej animacji. W przeciwnym razie system rysowania będzie komkładować widoki z inną zawartością, co może znacząco wpłynąć na wydajność.
Unikaj tłustych XIB
Chociaż XIB zostały w dużej mierze zastąpione przez scenorysy, istnieją pewne okoliczności, w których XIB mogą być nadal używane. Po załadowaniu pliku XIB do pamięci wszystkie jego zawartość są ładowane do pamięci, w tym do wszystkich obrazów. Jeśli XIB zawiera widok, który nie jest natychmiast używany, pamięć jest marnowana. W związku z tym w przypadku korzystania z xiB upewnij się, że istnieje tylko jeden identyfikator XIB na kontroler widoku, a jeśli to możliwe, rozdziel hierarchię widoku kontrolera widoku na oddzielne XIB.
Optymalizowanie zasobów obrazu
Obrazy to niektóre z najdroższych zasobów używanych przez aplikacje i są często przechwytywane w wysokiej rozdzielczości. W związku z tym podczas wyświetlania obrazu z pakietu aplikacji w elemecie UIImageView
upewnij się, że obraz i UIImageView
mają identyczny rozmiar. Skalowanie obrazów w czasie wykonywania może być kosztowną operacją, szczególnie jeśli UIImageView
element jest osadzony w obiekcie UIScrollView
.
Aby uzyskać więcej informacji, zobacz Optymalizowanie zasobów obrazów w przewodniku dotyczącym wydajności międzyplatformowych.
Testowanie na urządzeniach
Rozpocznij wdrażanie i testowanie aplikacji na urządzeniu fizycznym tak szybko, jak to możliwe. Symulatory nie pasują idealnie do zachowań i ograniczeń urządzeń, dlatego ważne jest, aby jak najszybciej przetestować scenariusz urządzenia w świecie rzeczywistym.
W szczególności symulator nie w żaden sposób symuluje ograniczeń pamięci ani procesora CPU urządzenia fizycznego.
Synchronizowanie animacji z odświeżaniem ekranu
Gry mają tendencję do ciasnych pętli do uruchamiania logiki gry i aktualizowania ekranu. Typowe szybkości klatek ramek wahają się od trzydziestu do sześćdziesiąt ramek na sekundę. Niektórzy deweloperzy uważają, że powinni aktualizować ekran tak wiele razy, jak to możliwe na sekundę, łącząc symulację gry z aktualizacjami ekranu i może być kuszony, aby przejść poza sześćdziesiąt klatek na sekundę.
Jednak serwer wyświetlania wykonuje aktualizacje ekranu na górnym limicie sześciudziesiąt razy na sekundę. W związku z tym próba zaktualizowania ekranu szybciej niż ten limit może prowadzić do rozerwania ekranu i mikrowycinania. Najlepiej jest strukturę kodu, aby aktualizacje ekranu zostały zsynchronizowane z aktualizacją wyświetlania. Można to osiągnąć przy użyciu CoreAnimation.CADisplayLink
klasy , która jest czasomierzem odpowiednim dla wizualizacji i gier, które działają na sześćdziesiąt klatek na sekundę.
Unikaj przezroczystości animacji rdzeni
Unikanie przezroczystości animacji rdzenia poprawia wydajność komponowania map bitowych. Ogólnie rzecz biorąc, unikaj przezroczystych warstw i rozmytych obramowań, jeśli to możliwe.
Unikanie generowania kodu
Należy unikać dynamicznego generowania kodu za pomocą System.Reflection.Emit
środowiska uruchomieniowego języka dynamicznego lub środowiska uruchomieniowego języka dynamicznego, ponieważ jądro systemu iOS uniemożliwia dynamiczne wykonywanie kodu.
Podsumowanie
W tym artykule opisano i omówiono techniki zwiększania wydajności aplikacji utworzonych za pomocą platformy Xamarin.iOS. Łącznie te techniki mogą znacznie zmniejszyć ilość pracy wykonywanej przez procesor CPU i ilość pamięci zużywanej przez aplikację.