Tingkatkan kinerja aplikasi
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 perangkat. 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.
Ada banyak teknik untuk meningkatkan performa, dan performa yang dirasakan, dari aplikasi .NET Multi-platform App UI (.NET MAUI). Secara kolektif teknik ini dapat sangat mengurangi jumlah pekerjaan yang dilakukan oleh CPU, dan jumlah memori yang dikonsumsi oleh aplikasi.
Menggunakan profiler
Saat mengembangkan aplikasi, penting untuk hanya mencoba mengoptimalkan kode setelah dibuat profilnya. Pembuatan profil adalah teknik untuk menentukan di mana pengoptimalan kode akan memiliki efek terbesar dalam mengurangi masalah performa. Profiler melacak penggunaan memori aplikasi, dan merekam waktu berjalan metode di aplikasi. Data ini membantu menavigasi melalui jalur eksekusi aplikasi, dan biaya eksekusi kode, sehingga peluang terbaik untuk pengoptimalan dapat ditemukan.
Aplikasi .NET MAUI dapat dibuat profilnya menggunakan dotnet-trace
di Android, iOS, dan Mac, dan Windows, dan dengan PerfView di Windows. Untuk informasi selengkapnya, lihat Membuat profil aplikasi .NET MAUI.
Praktik terbaik berikut direkomendasikan saat membuat profil aplikasi:
- Hindari membuat profil aplikasi dalam simulator, karena simulator dapat mendistorsi performa aplikasi.
- Idealnya, pembuatan profil harus dilakukan pada berbagai perangkat, karena mengambil pengukuran performa pada satu perangkat tidak akan selalu menunjukkan karakteristik performa perangkat lain. Namun, minimal, pembuatan profil harus dilakukan pada perangkat yang memiliki spesifikasi terendah yang diantisipasi.
- Tutup semua aplikasi lain untuk memastikan bahwa dampak penuh aplikasi yang sedang difilter sedang diukur, bukan aplikasi lain.
Menggunakan pengikatan yang dikompilasi
Pengikatan yang dikompilasi meningkatkan performa pengikatan data di aplikasi .NET MAUI dengan menyelesaikan ekspresi pengikatan pada waktu kompilasi, bukan pada waktu proses dengan pantulan. Mengkompilasi ekspresi pengikatan menghasilkan kode yang dikompilasi yang biasanya menyelesaikan pengikatan 8-20 kali lebih cepat daripada menggunakan pengikatan klasik. Untuk informasi selengkapnya, lihat Pengikatan yang dikompilasi.
Mengurangi pengikatan yang tidak perlu
Jangan gunakan pengikatan untuk konten yang dapat dengan mudah diatur secara statis. Tidak ada keuntungan dalam mengikat data yang tidak perlu terikat, karena pengikatan tidak hemat biaya. Misalnya, pengaturan Button.Text = "Accept"
memiliki lebih sedikit overhead daripada mengikat Button.Text ke properti viewmodel string
dengan nilai "Terima".
Pilih tata letak yang benar
Tata letak yang mampu menampilkan beberapa anak, tetapi hanya memiliki satu anak, sia-sia. Misalnya, contoh berikut menunjukkan VerticalStackLayout dengan satu anak:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<VerticalStackLayout>
<Image Source="waterfront.jpg" />
</VerticalStackLayout>
</ContentPage>
Ini boros dan VerticalStackLayout elemen harus dihapus, seperti yang ditunjukkan dalam contoh berikut:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<Image Source="waterfront.jpg" />
</ContentPage>
Selain itu, jangan mencoba mereproduksi tampilan tata letak tertentu dengan menggunakan kombinasi tata letak lain, karena ini menghasilkan perhitungan tata letak yang tidak perlu dilakukan. Misalnya, jangan mencoba mereproduksi Grid tata letak dengan menggunakan kombinasi HorizontalStackLayout elemen. Contoh berikut menunjukkan contoh praktik buruk ini:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<VerticalStackLayout>
<HorizontalStackLayout>
<Label Text="Name:" />
<Entry Placeholder="Enter your name" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Age:" />
<Entry Placeholder="Enter your age" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Occupation:" />
<Entry Placeholder="Enter your occupation" />
</HorizontalStackLayout>
<HorizontalStackLayout>
<Label Text="Address:" />
<Entry Placeholder="Enter your address" />
</HorizontalStackLayout>
</VerticalStackLayout>
</ContentPage>
Ini boros karena perhitungan tata letak yang tidak perlu dilakukan. Sebaliknya, tata letak yang diinginkan dapat dicapai dengan lebih baik menggunakan , seperti yang Gridditunjukkan dalam contoh berikut:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<Grid ColumnDefinitions="100,*"
RowDefinitions="30,30,30,30">
<Label Text="Name:" />
<Entry Grid.Column="1"
Placeholder="Enter your name" />
<Label Grid.Row="1"
Text="Age:" />
<Entry Grid.Row="1"
Grid.Column="1"
Placeholder="Enter your age" />
<Label Grid.Row="2"
Text="Occupation:" />
<Entry Grid.Row="2"
Grid.Column="1"
Placeholder="Enter your occupation" />
<Label Grid.Row="3"
Text="Address:" />
<Entry Grid.Row="3"
Grid.Column="1"
Placeholder="Enter your address" />
</Grid>
</ContentPage>
Mengoptimalkan sumber daya gambar
Gambar adalah beberapa sumber daya termahal yang digunakan aplikasi, dan sering ditangkap pada resolusi tinggi. Meskipun ini membuat gambar yang cerah penuh detail, aplikasi yang menampilkan gambar tersebut biasanya memerlukan lebih banyak penggunaan CPU untuk mendekode gambar dan lebih banyak memori untuk menyimpan gambar yang didekodekan. Menyia-nyiakan untuk mendekode gambar resolusi tinggi dalam memori ketika akan diturunkan skalanya ke ukuran yang lebih kecil untuk tampilan. Sebagai gantinya, kurangi penggunaan CPU dan jejak memori dengan membuat versi gambar tersimpan yang mendekati ukuran tampilan yang diprediksi. Misalnya, gambar yang ditampilkan dalam tampilan daftar kemungkinan besar akan menjadi resolusi yang lebih rendah daripada gambar yang ditampilkan di layar penuh.
Selain itu, gambar hanya boleh dibuat jika diperlukan dan harus dirilis segera setelah aplikasi tidak lagi memerlukannya. Misalnya, jika aplikasi menampilkan gambar dengan membaca datanya dari aliran, pastikan streaming dibuat hanya saat diperlukan, dan pastikan bahwa streaming dirilis saat tidak lagi diperlukan. Ini dapat dicapai dengan membuat aliran saat halaman dibuat, atau ketika Page.Appearing peristiwa diaktifkan, lalu membuang aliran saat Page.Disappearing peristiwa terjadi.
Saat mengunduh gambar untuk ditampilkan dengan ImageSource.FromUri(Uri) metode , pastikan gambar yang diunduh di-cache untuk jumlah waktu yang sesuai. Untuk informasi selengkapnya, lihat Penembolokan gambar.
Mengurangi ukuran pohon visual
Mengurangi jumlah elemen pada halaman akan membuat halaman dirender lebih cepat. Ada dua teknik utama untuk mencapai hal ini. Yang pertama adalah menyembunyikan elemen yang tidak terlihat. Properti IsVisible dari setiap elemen menentukan apakah elemen harus menjadi bagian dari pohon visual atau tidak. Oleh karena itu, jika elemen tidak terlihat karena disembunyikan di belakang elemen lain, hapus elemen atau atur propertinya IsVisible
ke false
.
Teknik kedua adalah menghapus elemen yang tidak perlu. Misalnya, berikut ini memperlihatkan tata letak halaman yang berisi beberapa Label elemen:
<VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Hello" />
</VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Welcome to the App!" />
</VerticalStackLayout>
<VerticalStackLayout Padding="20,20,0,0">
<Label Text="Downloading Data..." />
</VerticalStackLayout>
</VerticalStackLayout>
Tata letak halaman yang sama dapat dipertahankan dengan jumlah elemen yang dikurangi, seperti yang ditunjukkan dalam contoh berikut:
<VerticalStackLayout Padding="20,35,20,20"
Spacing="25">
<Label Text="Hello" />
<Label Text="Welcome to the App!" />
<Label Text="Downloading Data..." />
</VerticalStackLayout>
Mengurangi ukuran kamus sumber daya aplikasi
Sumber daya apa pun yang digunakan di seluruh aplikasi harus disimpan dalam kamus sumber daya aplikasi untuk menghindari duplikasi. Ini akan membantu mengurangi jumlah XAML yang harus diurai di seluruh aplikasi. Contoh berikut menunjukkan HeadingLabelStyle
sumber daya, yang digunakan di seluruh aplikasi, dan sebagainya didefinisikan dalam kamus sumber daya aplikasi:
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.App">
<Application.Resources>
<Style x:Key="HeadingLabelStyle"
TargetType="Label">
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="FontSize"
Value="Large" />
<Setter Property="TextColor"
Value="Red" />
</Style>
</Application.Resources>
</Application>
Namun, XAML yang khusus untuk halaman tidak boleh disertakan dalam kamus sumber daya aplikasi, karena sumber daya kemudian akan diurai saat startup aplikasi alih-alih ketika diperlukan oleh halaman. Jika sumber daya digunakan oleh halaman yang bukan halaman startup, sumber daya harus ditempatkan di kamus sumber daya untuk halaman tersebut, oleh karena itu membantu mengurangi XAML yang diurai saat aplikasi dimulai. Contoh berikut menunjukkan HeadingLabelStyle
sumber daya, yang hanya ada di satu halaman, dan sebagainya ditentukan dalam kamus sumber daya halaman:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyMauiApp.MainPage">
<ContentPage.Resources>
<Style x:Key="HeadingLabelStyle"
TargetType="Label">
<Setter Property="HorizontalOptions"
Value="Center" />
<Setter Property="FontSize"
Value="Large" />
<Setter Property="TextColor"
Value="Red" />
</Style>
</ContentPage.Resources>
...
</ContentPage>
Untuk informasi selengkapnya tentang sumber daya aplikasi, lihat Aplikasi gaya menggunakan XAML.
Mengurangi ukuran aplikasi
Saat .NET MAUI membuat aplikasi Anda, linker bernama ILLink dapat digunakan untuk mengurangi ukuran keseluruhan aplikasi. ILLink mengurangi ukuran dengan menganalisis kode menengah yang dihasilkan oleh pengkompilasi. Ini menghapus metode, properti, bidang, peristiwa, struktur, dan kelas yang tidak digunakan untuk menghasilkan aplikasi yang hanya berisi dependensi kode dan perakitan yang diperlukan untuk menjalankan aplikasi.
Untuk informasi selengkapnya tentang mengonfigurasi perilaku linker, lihat Menautkan aplikasi Android, Menautkan aplikasi iOS, dan Menautkan aplikasi Mac Catalyst.
Mengurangi periode aktivasi aplikasi
Semua aplikasi memiliki periode aktivasi, yang merupakan waktu antara saat aplikasi dimulai dan saat aplikasi siap digunakan. Periode aktivasi ini memberi pengguna kesan pertama aplikasi, sehingga penting untuk mengurangi periode aktivasi dan persepsi pengguna tentangnya, agar mereka mendapatkan kesan pertama yang menguntungkan dari aplikasi.
Sebelum aplikasi menampilkan UI awalnya, aplikasi harus menyediakan layar splash untuk menunjukkan kepada pengguna bahwa aplikasi dimulai. Jika aplikasi tidak dapat dengan cepat menampilkan UI awalnya, layar splash harus digunakan untuk memberi tahu pengguna tentang kemajuan melalui periode aktivasi, untuk menawarkan jaminan bahwa aplikasi belum digantung. Jaminan ini bisa menjadi bilah kemajuan, atau kontrol serupa.
Selama periode aktivasi, aplikasi menjalankan logika aktivasi, yang sering mencakup pemuatan dan pemrosesan sumber daya. Periode aktivasi dapat dikurangi dengan memastikan bahwa sumber daya yang diperlukan dikemas dalam aplikasi, alih-alih diambil dari jarak jauh. Misalnya, dalam beberapa keadaan mungkin sesuai selama periode aktivasi untuk memuat data tempat penampung yang disimpan secara lokal. Kemudian, setelah antarmuka pengguna awal ditampilkan, dan pengguna dapat berinteraksi dengan aplikasi, data tempat penampung dapat diganti secara progresif dari sumber jarak jauh. Selain itu, logika aktivasi aplikasi hanya boleh melakukan pekerjaan yang diperlukan untuk memungkinkan pengguna mulai menggunakan aplikasi. Ini dapat membantu jika menunda pemuatan rakitan tambahan, assembly dimuat saat pertama kali digunakan.
Pilih kontainer injeksi dependensi dengan hati-hati
Kontainer injeksi dependensi memperkenalkan batasan performa tambahan ke dalam aplikasi seluler. Mendaftarkan dan menyelesaikan jenis dengan kontainer memiliki biaya performa karena penggunaan refleksi kontainer untuk membuat setiap jenis, terutama jika dependensi sedang direkonstruksi untuk setiap navigasi halaman di aplikasi. Jika ada banyak atau dependensi mendalam, biaya pembuatan dapat meningkat secara signifikan. Selain itu, jenis pendaftaran, yang biasanya terjadi selama startup aplikasi, dapat berdampak nyata pada waktu startup, tergantung pada kontainer yang digunakan. Untuk informasi selengkapnya tentang injeksi dependensi di aplikasi .NET MAUI, lihat Injeksi dependensi.
Sebagai alternatif, injeksi dependensi dapat dibuat lebih berkinerja dengan menerapkannya secara manual menggunakan pabrik.
Membuat aplikasi Shell
Aplikasi .NET MAUI Shell memberikan pengalaman navigasi berpendapat berdasarkan flyout dan tab. Jika pengalaman pengguna aplikasi Anda dapat diimplementasikan dengan Shell, akan bermanfaat untuk melakukannya. Aplikasi Shell membantu menghindari pengalaman startup yang buruk, karena halaman dibuat sesuai permintaan sebagai respons terhadap navigasi daripada saat startup aplikasi, yang terjadi dengan aplikasi yang menggunakan TabbedPage. Untuk informasi selengkapnya, lihat Gambaran umum Shell.
Optimalkan performa ListView
Saat menggunakan ListView, ada sejumlah pengalaman pengguna yang harus dioptimalkan:
- Inisialisasi – interval waktu dimulai saat kontrol dibuat, dan berakhir saat item ditampilkan di layar.
- Menggulir – kemampuan untuk menggulir daftar dan memastikan bahwa UI tidak tertinggal dari gerakan sentuh.
- Interaksi untuk menambahkan, menghapus, dan memilih item.
ListView Kontrol memerlukan aplikasi untuk menyediakan data dan templat sel. Bagaimana hal ini dicapai akan berdampak besar pada performa kontrol. Untuk informasi selengkapnya, lihat Data cache.
Menggunakan pemrograman asinkron
Respons keseluruhan aplikasi Anda dapat ditingkatkan, dan penyempitan performa sering dihindari, dengan menggunakan pemrograman asinkron. Di .NET, Pola Asinkron Berbasis Tugas (TAP) adalah pola desain yang direkomendasikan untuk operasi asinkron. Namun, penggunaan TAP yang salah dapat mengakibatkan aplikasi yang tidak format.
Fundamental
Panduan umum berikut harus diikuti saat menggunakan TAP:
- Pahami siklus hidup tugas, yang diwakili oleh
TaskStatus
enumerasi. Untuk informasi selengkapnya, lihat Arti status TaskStatus dan Tugas. Task.WhenAll
Gunakan metode untuk secara asinkron menunggu beberapa operasi asinkron selesai, daripada satuawait
per satu serangkaian operasi asinkron. Untuk informasi selengkapnya, lihat Task.WhenAll.Task.WhenAny
Gunakan metode untuk secara asinkron menunggu salah satu dari beberapa operasi asinkron selesai. Untuk informasi selengkapnya, lihat Task.WhenAny.Task.Delay
Gunakan metode untuk menghasilkanTask
objek yang selesai setelah waktu yang ditentukan. Ini berguna untuk skenario seperti polling untuk data, dan menunda penanganan input pengguna untuk waktu yang telah ditentukan. Untuk informasi selengkapnya, lihat Task.Delay.- Jalankan operasi CPU sinkron intensif pada kumpulan utas dengan
Task.Run
metode . Metode ini adalah pintasan untukTaskFactory.StartNew
metode , dengan kumpulan argumen yang paling optimal. Untuk informasi selengkapnya, lihat Task.Run. - Hindari mencoba membuat konstruktor asinkron. Sebagai gantinya, gunakan peristiwa siklus hidup atau logika inisialisasi terpisah untuk menginisialisasi apa pun dengan benar
await
. Untuk informasi selengkapnya, lihat Konstruktor Asinkron di blog.stephencleary.com. - Gunakan pola tugas malas untuk menghindari menunggu operasi asinkron selesai selama startup aplikasi. Untuk informasi selengkapnya, lihat AsyncLazy.
- Buat pembungkus tugas untuk operasi asinkron yang ada, yang tidak menggunakan TAP, dengan membuat
TaskCompletionSource<T>
objek. Objek-objek ini mendapatkan manfaat dari kemampuan pemrogramanTask
, dan memungkinkan Anda mengontrol masa pakai dan penyelesaian .Task
Untuk informasi selengkapnya, lihat Sifat TaskCompletionSource. Task
Mengembalikan objek, alih-alih mengembalikan objek yang ditungguTask
, ketika tidak perlu memproses hasil operasi asinkron. Ini lebih berkinerja karena lebih sedikit pengalihan konteks yang dilakukan.- Gunakan pustaka Aliran Data Pustaka Paralel Tugas (TPL) dalam skenario seperti memproses data saat tersedia, atau ketika Anda memiliki beberapa operasi yang harus berkomunikasi satu sama lain secara asinkron. Untuk informasi selengkapnya, lihat Aliran Data (Pustaka Paralel Tugas).
UI
Panduan berikut harus diikuti saat menggunakan TAP dengan kontrol UI:
Panggil api versi asinkron, jika tersedia. Ini akan membuat utas UI tidak diblokir, yang akan membantu meningkatkan pengalaman pengguna dengan aplikasi.
Perbarui elemen UI dengan data dari operasi asinkron pada utas UI, untuk menghindari pengecualian yang dilemparkan. Namun, pembaruan pada
ListView.ItemsSource
properti akan secara otomatis dinamai ke utas UI. Untuk informasi tentang menentukan apakah kode berjalan pada utas UI, lihat Membuat utas pada utas UI.Penting
Setiap properti kontrol yang diperbarui melalui pengikatan data akan secara otomatis di-marshal ke utas UI.
Penanganan kesalahan
Panduan penanganan kesalahan berikut harus diikuti saat menggunakan TAP:
- Pelajari tentang penanganan pengecualian asinkron. Pengecualian yang tidak tertangani yang dilemparkan oleh kode yang berjalan secara asinkron disebarkan kembali ke utas panggilan, kecuali dalam skenario tertentu. Untuk informasi selengkapnya, lihat Penanganan pengecualian (Pustaka Paralel Tugas).
- Hindari membuat
async void
metode, dan sebagai gantinya membuatasync Task
metode. Ini memungkinkan penanganan kesalahan, komposabilitas, dan kemampuan uji yang lebih mudah. Pengecualian untuk pedoman ini adalah penanganan aktivitas asinkron, yang harus mengembalikanvoid
. Untuk informasi selengkapnya, lihat Menghindari Async Void. - Jangan mencampur pemblokiran dan kode asinkron dengan memanggil
Task.Wait
metode ,Task.Result
, atauGetAwaiter().GetResult
, karena dapat mengakibatkan kebuntuan terjadi. Namun, jika pedoman ini harus dilanggar, pendekatan yang disukai adalah memanggilGetAwaiter().GetResult
metode karena mempertahankan pengecualian tugas. Untuk informasi selengkapnya, lihat Asinkron Semua Cara dan Penanganan Pengecualian Tugas di .NET 4.5. ConfigureAwait
Gunakan metode jika memungkinkan, untuk membuat kode bebas konteks. Kode bebas konteks memiliki performa yang lebih baik untuk aplikasi seluler dan merupakan teknik yang berguna untuk menghindari kebuntuan saat bekerja dengan basis kode asinkron sebagian. Untuk informasi selengkapnya, lihat Mengonfigurasi Konteks.- Gunakan tugas kelanjutan untuk fungsionalitas seperti menangani pengecualian yang dilemparkan oleh operasi asinkron sebelumnya, dan membatalkan kelanjutan baik sebelum dimulai atau saat dijalankan. Untuk informasi selengkapnya, lihat Menautkan Tugas dengan Menggunakan Tugas Berkelanjutan.
- Gunakan implementasi asinkron ICommand saat operasi asinkron dipanggil dari ICommand. Ini memastikan bahwa setiap pengecualian dalam logika perintah asinkron dapat ditangani. Untuk informasi selengkapnya, lihat Pemrograman Asinkron: Pola untuk Aplikasi MVVM Asinkron: Perintah.
Menunda biaya pembuatan objek
Inisialisasi malas dapat digunakan untuk menunda pembuatan objek sampai pertama kali digunakan. Teknik ini terutama digunakan untuk meningkatkan performa, menghindari komputasi, dan mengurangi persyaratan memori.
Pertimbangkan untuk menggunakan inisialisasi malas untuk objek yang mahal untuk dibuat dalam skenario berikut:
- Aplikasi mungkin tidak menggunakan objek .
- Operasi mahal lainnya harus diselesaikan sebelum objek dibuat.
Kelas Lazy<T>
ini digunakan untuk menentukan jenis yang diinisialisasi malas, seperti yang ditunjukkan dalam contoh berikut:
void ProcessData(bool dataRequired = false)
{
Lazy<double> data = new Lazy<double>(() =>
{
return ParallelEnumerable.Range(0, 1000)
.Select(d => Compute(d))
.Aggregate((x, y) => x + y);
});
if (dataRequired)
{
if (data.Value > 90)
{
...
}
}
}
double Compute(double x)
{
...
}
Inisialisasi malas terjadi saat pertama kali Lazy<T>.Value
properti diakses. Jenis yang dibungkus dibuat dan dikembalikan pada akses pertama, dan disimpan untuk akses di masa mendatang.
Untuk informasi selengkapnya tentang inisialisasi malas, lihat Inisialisasi Malas.
Merilis sumber daya IDisposable
Antarmuka IDisposable
menyediakan mekanisme untuk merilis sumber daya. Ini menyediakan Dispose
metode yang harus diimplementasikan untuk secara eksplisit merilis sumber daya. IDisposable
bukan destruktor, dan hanya boleh diimplementasikan dalam keadaan berikut:
- Ketika kelas memiliki sumber daya yang tidak dikelola. Sumber daya umum yang tidak terkelola yang memerlukan penghapusan mencakup file, aliran, dan koneksi jaringan.
- Saat kelas memiliki sumber daya terkelola
IDisposable
.
Konsumen jenis kemudian dapat memanggil IDisposable.Dispose
implementasi ke sumber daya gratis ketika instans tidak lagi diperlukan. Ada dua pendekatan untuk mencapai hal ini:
- Dengan membungkus
IDisposable
objek dalam pernyataanusing
. - Dengan membungkus panggilan ke
IDisposable.Dispose
dalamtry
/finally
blok.
Membungkus objek IDisposable dalam pernyataan penggunaan
Contoh berikut menunjukkan cara membungkus IDisposable
objek dalam using
pernyataan:
public void ReadText(string filename)
{
string text;
using (StreamReader reader = new StreamReader(filename))
{
text = reader.ReadToEnd();
}
...
}
Kelas StreamReader
mengimplementasikan IDisposable
, dan using
pernyataan menyediakan sintaks yang nyaman yang memanggil StreamReader.Dispose
metode pada StreamReader
objek sebelum keluar dari cakupan. using
Dalam blok, StreamReader
objek bersifat baca-saja dan tidak dapat ditetapkan ulang. Pernyataan ini using
juga memastikan bahwa Dispose
metode dipanggil bahkan jika terjadi pengecualian, karena kompilator mengimplementasikan bahasa perantara (IL) untuk blok/try
finally
.
Bungkus panggilan ke IDisposable.Dispose dalam blok try/finally
Contoh berikut menunjukkan cara membungkus panggilan ke IDisposable.Dispose
dalam try
/finally
blok:
public void ReadText(string filename)
{
string text;
StreamReader reader = null;
try
{
reader = new StreamReader(filename);
text = reader.ReadToEnd();
}
finally
{
if (reader != null)
reader.Dispose();
}
...
}
Kelas StreamReader
mengimplementasikan IDisposable
, dan finally
blok memanggil metode untuk merilis StreamReader.Dispose
sumber daya. Untuk informasi selengkapnya, lihat Antarmuka IDisposable.
Berhenti berlangganan dari peristiwa
Untuk mencegah kebocoran memori, peristiwa harus berhenti berlangganan sebelum objek pelanggan dibuang. Hingga peristiwa berhenti berlangganan, delegasi untuk peristiwa di objek penerbitan memiliki referensi ke delegasi yang merangkum penanganan aktivitas pelanggan. Selama objek penerbitan memegang referensi ini, pengumpulan sampah tidak akan mengklaim kembali memori objek pelanggan.
Contoh berikut menunjukkan cara berhenti berlangganan dari peristiwa:
public class Publisher
{
public event EventHandler MyEvent;
public void OnMyEventFires()
{
if (MyEvent != null)
MyEvent(this, EventArgs.Empty);
}
}
public class Subscriber : IDisposable
{
readonly Publisher _publisher;
public Subscriber(Publisher publish)
{
_publisher = publish;
_publisher.MyEvent += OnMyEventFires;
}
void OnMyEventFires(object sender, EventArgs e)
{
Debug.WriteLine("The publisher notified the subscriber of an event");
}
public void Dispose()
{
_publisher.MyEvent -= OnMyEventFires;
}
}
Kelas Subscriber
berhenti berlangganan dari peristiwa dalam metodenya Dispose
.
Siklus referensi juga dapat terjadi saat menggunakan penanganan aktivitas dan sintaks lambda, karena ekspresi lambda dapat mereferensikan dan menjaga objek tetap hidup. Oleh karena itu, referensi ke metode anonim dapat disimpan di bidang dan digunakan untuk berhenti berlangganan dari peristiwa, seperti yang ditunjukkan dalam contoh berikut:
public class Subscriber : IDisposable
{
readonly Publisher _publisher;
EventHandler _handler;
public Subscriber(Publisher publish)
{
_publisher = publish;
_handler = (sender, e) =>
{
Debug.WriteLine("The publisher notified the subscriber of an event");
};
_publisher.MyEvent += _handler;
}
public void Dispose()
{
_publisher.MyEvent -= _handler;
}
}
Bidang _handler
mempertahankan referensi ke metode anonim, dan digunakan untuk langganan peristiwa dan berhenti berlangganan.
Hindari referensi melingkar yang kuat di iOS dan Mac Catalyst
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 NSObject
kontainer -turunan dan sangat dirujuk dari Objective-C, seperti yang ditunjukkan dalam contoh berikut:
class Container : UIView
{
public void Poke()
{
// Call this method to poke this object
}
}
class MyView : UIView
{
Container _parent;
public MyView(Container parent)
{
_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 .NET untuk iOS membuat instans GCHandle
untuk menjaga MyView
objek dalam kode terkelola tetap hidup, karena tidak ada jaminan bahwa objek terkelola apa pun akan menyimpan referensi ke objek tersebut. Dari perspektif kode terkelola, MyView
objek akan diklaim kembali setelah AddSubview(UIView) 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:
- Hindari referensi melingkar dengan menyimpan referensi lemah ke kontainer.
- Panggil
Dispose
pada objek. - Putuskan siklus secara manual dengan mengatur tautan ke kontainer ke
null
. - Hapus objek yang terkandung secara manual dari kontainer.
Menggunakan referensi yang lemah
Salah satu cara untuk mencegah siklus adalah dengan menggunakan referensi lemah dari anak ke induk. Misalnya, kode di atas bisa seperti yang ditunjukkan dalam contoh berikut:
class Container : UIView
{
public void Poke()
{
// Call this method to poke this object
}
}
class MyView : UIView
{
WeakReference<Container> _weakParent;
public MyView(Container parent)
{
_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 ke 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
.
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 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)
{
_container = container;
}
public override void Dispose()
{
_container = null;
}
}