Performa Xamarin.iOS

Performa aplikasi yang buruk menyajikan dirinya dalam banyak hal. Ini dapat membuat aplikasi tampak tidak responsif, dapat menyebabkan pengguliran lambat, dan dapat mengurangi masa pakai baterai. Namun, mengoptimalkan performa melibatkan lebih dari sekadar menerapkan kode yang efisien. Pengalaman pengguna tentang performa aplikasi juga harus dipertimbangkan. Misalnya, memastikan bahwa operasi dijalankan tanpa memblokir pengguna untuk melakukan aktivitas lain dapat membantu meningkatkan pengalaman pengguna.

Dokumen ini menjelaskan teknik yang dapat digunakan untuk meningkatkan performa dan penggunaan memori dalam aplikasi Xamarin.iOS.

Catatan

Sebelum membaca artikel ini, Anda harus terlebih dahulu membaca Performa Lintas Platform, yang membahas teknik khusus non-platform untuk meningkatkan penggunaan memori dan performa aplikasi yang dibangun menggunakan platform Xamarin.

Hindari referensi melingkar yang kuat

Dalam beberapa situasi dimungkinkan untuk membuat siklus referensi yang kuat yang dapat mencegah objek mendapatkan kembali memori mereka oleh pengumpul sampah. Misalnya, pertimbangkan kasus di mana NSObjectsubkelas -turunan, seperti kelas yang mewarisi dari UIView, ditambahkan ke NSObjectkontainer -turunan dan sangat direferensikan dari Objective-C, seperti yang ditunjukkan dalam contoh kode berikut:

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

Ketika kode ini membuat instans Container , objek C# akan memiliki referensi yang Objective-C kuat ke objek. Demikian pula, MyView instans juga akan memiliki referensi yang Objective-C kuat ke objek.

Selain itu, panggilan ke container.AddSubview akan meningkatkan jumlah referensi pada instans yang tidak dikelola MyView . Ketika ini terjadi, runtime Xamarin.iOS membuat instans GCHandle untuk menjaga MyView objek dalam kode terkelola tetap hidup, karena tidak ada jaminan bahwa objek terkelola akan menyimpan referensi ke sana. Dari perspektif kode terkelola, MyView objek akan diklaim kembali setelah AddSubview panggilan bukan untuk GCHandle.

Objek yang tidak dikelola MyView akan memiliki GCHandle penunjuk ke objek terkelola, yang dikenal sebagai tautan yang kuat. Objek terkelola akan berisi referensi ke Container instans. Pada gilirannya Container instans akan memiliki referensi terkelola ke MyView objek.

Dalam keadaan di mana objek yang terkandung menyimpan tautan ke kontainernya, ada beberapa opsi yang tersedia untuk menangani referensi melingkar:

  • Putuskan siklus secara manual dengan mengatur tautan ke kontainer ke null.
  • Hapus objek yang terkandung secara manual dari kontainer.
  • Panggil Dispose pada objek.
  • Hindari referensi melingkar yang menyimpan referensi yang lemah ke kontainer. Untuk informasi selengkapnya tentang referensi yang lemah.

Menggunakan WeakReferences

Salah satu cara untuk mencegah siklus adalah dengan menggunakan referensi lemah dari anak ke induk. Misalnya, kode di atas dapat ditulis seperti ini:

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

Di sini, objek yang terkandung tidak akan menjaga induk tetap hidup. Namun, induk menjaga anak tetap hidup melalui panggilan yang dilakukan untuk container.AddSubView.

Ini juga terjadi di API iOS yang menggunakan pola delegasi atau sumber data, di mana kelas peer berisi implementasi; misalnya, saat mengatur Delegate properti atau DataSource di UITableView kelas .

Dalam kasus kelas yang dibuat murni demi menerapkan protokol, misalnya IUITableViewDataSource, apa yang dapat Anda lakukan adalah alih-alih membuat subkelas, Anda hanya dapat mengimplementasikan antarmuka di kelas dan mengambil alih metode, dan menetapkan DataSource properti ke this.

Atribut lemah

Xamarin.iOS 11.10 memperkenalkan [Weak] atribut . Seperti WeakReference <T>, [Weak] dapat digunakan untuk memecahkan referensi melingkar yang kuat, tetapi dengan kode yang lebih sedikit.

Pertimbangkan kode berikut, yang menggunakan 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 ();
        }
    }
}

Kode yang setara menggunakan [Weak] jauh lebih ringkas:

public class MyFooDelegate : FooDelegate {
    [Weak] MyViewController controller;
    public MyFooDelegate (MyViewController ctrl) => controller = ctrl;
    public void CallDoSomething () => controller.DoSomething ();
}

Berikut ini adalah contoh lain penggunaan [Weak] dalam konteks pola delegasi :

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

Membuang objek dengan referensi yang kuat

Jika ada referensi yang kuat dan sulit untuk menghapus dependensi, buat Dispose metode hapus pointer induk.

Untuk kontainer, ambil alih Dispose metode untuk menghapus objek yang terkandung, seperti yang ditunjukkan dalam contoh kode berikut:

class MyContainer : UIView
{
    public override void Dispose ()
    {
        // Brute force, remove everything
        foreach (var view in Subviews)
        {
              view.RemoveFromSuperview ();
        }
        base.Dispose ();
    }
}

Untuk objek anak yang menyimpan referensi yang kuat ke induknya, hapus referensi ke induk dalam Dispose implementasi:

class MyChild : UIView
{
    MyContainer container;
    public MyChild (MyContainer container)
    {
        this.container = container;
    }
    public override void Dispose ()
    {
        container = null;
    }
}

Untuk informasi selengkapnya tentang merilis referensi yang kuat, lihat Merilis Sumber Daya yang Dapat Diubah I. Ada juga lebih banyak informasi pengumpulan sampah di sini.

Informasi selengkapnya

Untuk informasi selengkapnya, lihat Aturan untuk Menghindari Pertahankan Siklus pada Kakao Dengan Cinta, Apakah ini bug di MonoTouch GC di StackOverflow, dan Mengapa GC MonoTouch tidak dapat membunuh objek terkelola dengan refcount > 1? di StackOverflow.

Mengoptimalkan tampilan tabel

Pengguna mengharapkan pengguliran yang lancar dan waktu pemuatan yang cepat untuk UITableView instans. Namun, performa gulir dapat menderita ketika sel berisi hierarki tampilan berlapis dalam, atau ketika sel berisi tata letak yang kompleks. Namun, ada teknik yang dapat digunakan untuk menghindari performa yang buruk UITableView :

  • Gunakan kembali sel. Untuk informasi selengkapnya, lihat Menggunakan Kembali Sel.
  • Kurangi jumlah subview.
  • Konten sel cache yang diambil dari layanan web.
  • Cache tinggi baris apa pun jika tidak identik.
  • Buat sel, dan tampilan lainnya, buram.
  • Hindari penskalakan dan gradien gambar.

Secara kolektif teknik ini dapat membantu menjaga agar instans tetap UITableView lancar.

Gunakan kembali sel

Saat menampilkan ratusan baris dalam , UITableViewitu akan menjadi pemborosan memori untuk membuat ratusan UITableViewCell objek ketika hanya sejumlah kecil yang ditampilkan di layar sekaligus. Sebagai gantinya, hanya sel yang terlihat di layar yang dapat dimuat ke dalam memori, dengan konten yang dimuat ke dalam sel yang digunakan kembali ini. Ini mencegah instansiasi ratusan objek tambahan, menghemat waktu dan memori.

Oleh karena itu, ketika sel menghilang dari layar tampilannya dapat ditempatkan dalam antrean untuk digunakan kembali, seperti yang ditunjukkan dalam contoh kode berikut:

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

Saat pengguna menggulir, UITableView memanggil penimpaan GetCell untuk meminta tampilan baru ditampilkan. Penimpaan DequeueReusableCell ini kemudian memanggil metode dan jika sel tersedia untuk digunakan kembali, maka akan dikembalikan.

Untuk informasi selengkapnya, lihat Penggunaan Kembali Sel di Mengisi Tabel dengan Data.

Menggunakan tampilan buram

Pastikan bahwa tampilan apa pun yang tidak memiliki transparansi yang ditentukan memiliki kumpulan propertinya Opaque . Ini akan memastikan bahwa tampilan dirender secara optimal oleh sistem gambar. Ini sangat penting ketika tampilan disematkan dalam UIScrollView, atau merupakan bagian dari animasi yang kompleks. Jika tidak, sistem gambar akan menggabungkan tampilan dengan konten lain, yang dapat sangat berdampak pada performa.

Hindari XIB lemak

Meskipun XIB sebagian besar telah digantikan oleh papan cerita, ada beberapa keadaan di mana XIB mungkin masih digunakan. Ketika XIB dimuat ke dalam memori, semua kontennya dimuat ke dalam memori, termasuk gambar apa pun. Jika XIB berisi tampilan yang tidak segera digunakan, maka memori sedang disia-siakan. Oleh karena itu, saat menggunakan XIB memastikan bahwa hanya ada satu XIB per pengontrol tampilan, dan jika memungkinkan, pisahkan hierarki tampilan pengontrol tampilan menjadi XIB terpisah.

Mengoptimalkan sumber daya gambar

Gambar adalah beberapa sumber daya termahal yang digunakan aplikasi, dan sering ditangkap pada resolusi tinggi. Oleh karena itu, saat menampilkan gambar dari bundel aplikasi dalam UIImageView, pastikan bahwa gambar dan UIImageView berukuran identik. Menskalakan gambar saat runtime bisa menjadi operasi yang mahal, terutama jika UIImageView disematkan dalam UIScrollView.

Untuk informasi selengkapnya, lihat Mengoptimalkan Sumber Daya Gambar di panduan Performa Lintas Platform.

Uji pada perangkat

Mulai sebarkan dan uji aplikasi pada perangkat fisik sedini mungkin. Simulator tidak sangat cocok dengan perilaku dan batasan perangkat sehingga penting untuk menguji dalam skenario perangkat dunia nyata sedini mungkin.

Secara khusus simulator tidak dengan cara apa pun mensimulasikan pembatasan memori atau CPU perangkat fisik.

Menyinkronkan animasi dengan refresh tampilan

Game cenderung memiliki perulangan yang ketat untuk menjalankan logika game dan memperbarui layar. Kecepatan bingkai umum berkisar dari tiga puluh hingga enam puluh bingkai per detik. Beberapa pengembang merasa bahwa mereka harus memperbarui layar sebanyak mungkin per detik, menggabungkan simulasi game mereka dengan pembaruan pada layar dan mungkin tergoda untuk melampaui enam puluh bingkai per detik.

Namun, server tampilan melakukan pembaruan layar pada batas atas enam puluh kali per detik. Oleh karena itu, mencoba memperbarui layar lebih cepat dari batas ini dapat menyebabkan layar merobek dan gagap mikro. Yang terbaik adalah menyusun kode sehingga pembaruan layar disinkronkan dengan pembaruan tampilan. Ini dapat dicapai dengan menggunakan CoreAnimation.CADisplayLink kelas , yang merupakan timer yang cocok untuk visualisasi dan game yang berjalan pada enam puluh bingkai per detik.

Hindari transparansi Animasi Inti

Menghindari transparansi animasi inti meningkatkan performa komposit bitmap. Secara umum, hindari lapisan transparan dan batas kabur jika memungkinkan.

Hindari pembuatan kode

Membuat kode secara dinamis dengan System.Reflection.Emit atau Dynamic Language Runtime harus dihindari karena kernel iOS mencegah eksekusi kode dinamis.

Ringkasan

Artikel ini menjelaskan dan membahas teknik untuk meningkatkan performa aplikasi yang dibangun dengan Xamarin.iOS. Secara kolektif teknik ini dapat sangat mengurangi jumlah pekerjaan yang dilakukan oleh CPU, dan jumlah memori yang dikonsumsi oleh aplikasi.