提升應用程式效能

應用程式效能不佳以多種方式呈現。 它可以讓應用程式看起來沒有回應,可能會導致捲動緩慢,並可以減少裝置的電池使用時間。 不過,最佳化效能不僅僅只牽涉到實作有效率的程式碼而已。 也必須考慮應用程式效能的用戶體驗。 例如,確保作業能在不封鎖使用者執行其他活動的情況下執行,將可以協助改善使用者體驗。

.NET 多平臺應用程式 UI (.NET MAUI) 應用程式的效能和感知效能有許多技術。 這些技術可以大幅減少 CPU 所執行的工作量,以及應用程式所耗用的記憶體數量。

使用分析工具

開發應用程式時,請務必在分析程式碼之後,只嘗試優化程序代碼。 分析是一個決定程式碼在何處進行最佳化會帶來最大效果,進而減少效能問題的技術。 分析工具會追蹤應用程式的記憶體使用量,並記錄應用程式中方法的運行時間。 此數據有助於瀏覽應用程式的執行路徑,以及程式碼的執行成本,以便探索優化的最佳機會。

.NET MAUI 應用程式可以在 Android、iOS 和 Mac 和 Windows 上使用,以及 Windows 上的 PerfView 來分析 dotnet-trace 。 如需詳細資訊,請參閱 分析 .NET MAUI 應用程式

當在分析應用程式時,建議採取下列最佳做法:

  • 避免在模擬器中分析應用程式,因為模擬器可能會扭曲應用程式效能。
  • 在理想情況下,分析應在不同的裝置上分別執行,因為在一種裝置上測量效能不會總是顯示其他裝置的效能特性。 然而,以最低需求來說,分析應在擁有最低預期規格的裝置上執行。
  • 關閉所有其他應用程式,以確保要分析之應用程式的完整影響正在測量,而不是其他應用程式。

使用編譯的繫結

編譯的系結可藉由在編譯時期解析系結運算式,而不是在運行時間使用反映來改善 .NET MAUI 應用程式中的數據系結效能。 編譯繫結運算式會產生編譯的程式碼,通常會使用比傳統繫結快上 8-20 倍的速度來解析繫結。 如需詳細資訊,請參閱 編譯的系結。

減少不必要的繫結

請勿針對能輕易靜態設定的內容使用繫結。 將不需繫結的資料繫結並不會帶來任何好處,因為繫結本身並不符合成本效益。 例如,設定 Button.Text = "Accept" 的額外負荷比系結 Button.Text 至值為 “Accept” 的 viewmodel string 屬性還少。

選擇正確的版面配置

能顯示多個子系但卻只有單一子系的版面配置,是很浪費資源的。 例如,下列範例顯示 VerticalStackLayout 具有單一子系的 :

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

這是浪費的, VerticalStackLayout 而且應該移除元素,如下列範例所示:

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

此外,請勿嘗試使用其他版面配置的組合來重現特定版面配置的外觀,這會導致執行不必要的版面配置計算。 例如,請勿嘗試使用元素的組合來重現 Grid 版面配置 HorizontalStackLayout 。 下列範例示範此不良做法的範例:

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

這很浪費資源,因為會執行不必要的版面配置計算。 相反地,想要的版面配置可以更妥善地使用 Grid來達成,如下列範例所示:

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

最佳化影像資源

影像是應用程式使用的一些最昂貴的資源,而且通常會以高解析度擷取。 雖然這會建立充滿詳細數據的充滿活力的影像,但顯示這類影像的應用程式通常需要更多 CPU 使用量來譯碼影像和更多記憶體來儲存已譯碼的影像。 當影像可以縮小以供顯示之用時,在記憶體中解碼高解析度的影像便相當浪費。 相反地,建立接近預測顯示大小的預存映像版本,以減少CPU使用量和記憶體使用量。 例如,相較於在全螢幕中顯示的影像,在清單檢視中顯示的影像應為較低的解析度。

此外,只有在需要時才會建立映像,而且應該在應用程式不再需要時立即釋出映像。 例如,如果應用程式藉由從數據流讀取影像來顯示影像,請確定只有在需要串流時才會建立數據流,並確保不再需要數據流時才會釋放。 若要實現此目標,可藉由在頁面建立時或 Page.Appearing 事件觸發時建立資料流,然後在 Page.Disappearing 事件觸發時處置資料流。

下載影像以使用 ImageSource.FromUri(Uri) 方法顯示時,請確定已下載的影像會快取適當的時間。 如需詳細資訊,請參閱 映射快取

減少視覺化樹狀結構大小

減少頁面上的元素數目,可讓頁面轉譯速度變得更快。 有兩個主要技巧可以實現此目標。 其一是隱藏看不見的元素。 每個元素的 IsVisible 屬性會決定該元素是否應成為視覺化樹狀結構的一部分。 因此,如果某個元素因為隱藏在其他元素背後而看不見,請移除該元素或將其 IsVisible 屬性設為 false

第二個技巧是移除不必要的元素。 例如,下列顯示包含多個 Label 元素的頁面版面配置:

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

相同的版面配置可以使用減少的專案計數來維護,如下列範例所示:

<VerticalStackLayout Padding="20,35,20,20"
                     Spacing="25">
    <Label Text="Hello" />
    <Label Text="Welcome to the App!" />
    <Label Text="Downloading Data..." />
</VerticalStackLayout>

減少應用程式資源字典大小

在應用程式中使用的任何資源都應該儲存在應用程式的資源字典中,以避免重複。 這有助於減少整個應用程式必須剖析的 XAML 數量。 下列範例顯示 HeadingLabelStyle 整個應用程式所使用的資源,因此定義於應用程式的資源字典中:

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

不過,網頁專屬的 XAML 不應該包含在應用程式的資源字典中,因為資源會在應用程式啟動時剖析,而不是頁面需要時剖析。 如果資源是由不是啟動頁面的頁面使用,它應該放在該頁面的資源字典中,因此有助於減少應用程式啟動時剖析的 XAML。 下列範例顯示 HeadingLabelStyle 只位於單一頁面上的資源,因此定義於頁面的資源字典中:

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

如需應用程式資源的詳細資訊,請參閱 使用 XAML 設定應用程式樣式。

減少應用程式的大小

當 .NET MAUI 建置您的應用程式時,可以使用名為 ILLink 的連結器來減少應用程式的整體大小。 ILLink藉由分析編譯程式所產生的中繼程式代碼來減少大小。 它會移除未使用的方法、屬性、欄位、事件、結構及類別,以產生只包含執行應用程式所需的程式代碼和元件相依性的應用程式。

如需設定連結器行為的詳細資訊,請參閱 連結 Android 應用程式連結 iOS 應用程式,以及 連結 Mac Catalyst 應用程式

減少應用程式啟用期間

所有應用程式都有啟用 期間,也就是啟動應用程式的時間,以及應用程式可供使用的時間。 此啟用期間為使用者提供了對應用程式的第一印象,因此請務必減少啟用期間和使用者對其的看法,以便他們獲得對應用程式的有利第一印象。

在應用程式顯示其初始UI之前,它應該提供啟動顯示畫面,向使用者指出應用程式正在啟動。 如果應用程式無法快速顯示其初始UI,則啟動顯示畫面應該用來通知使用者透過啟用期間進行進度,以確保應用程式沒有停止回應。 這個讓使用者放心的措施可以是一個進度列或類似的控制項。

在啟用期間,應用程式會執行啟用邏輯,這通常包含資源的載入和處理。 啟用期間可透過確認必要的資源已和應用程式一同封裝,而非從遠端擷取來減少。 例如,在某些情況下,在啟用期間載入本機儲存的預留位置資料可能會是合適的選擇。 然後,當初始 UI 顯示時,使用者便可以與應用程式互動,並且預留位置資料也能從遠端來源逐漸取代。 此外,應用程式的啟用邏輯應該只會執行讓用戶開始使用應用程式所需的工作。 延遲載入其他組件可能會有幫助,因為組件會在第一次使用時才進行載入。

謹慎選擇相依性插入容器

相依性插入容器會將額外的效能限制引入行動應用程式。 使用容器來登錄和解析類型具有效能成本,因為容器會使用反映來建立每種類型,特別是在針對應用程式中的每個頁面巡覽重建相依性時。 如果有許多或深度相依性,則建立的成本可能會大幅增加。 此外,通常發生在應用程式啟動期間的類型註冊可能會對啟動時間造成明顯影響,視所使用的容器而定。 如需 .NET MAUI 應用程式中相依性插入的詳細資訊,請參閱 相依性插入

替代方法是使用 Factory 手動實作相依性插入,使其更具效能。

建立殼層應用程式

.NET MAUI Shell 應用程式會根據飛出視窗和索引標籤提供有意見的瀏覽體驗。 如果您的應用程式用戶體驗可以使用Shell來實作,這樣做會很有説明。 殼層應用程式有助於避免啟動體驗不佳,因為頁面會視需要建立,以響應流覽,而不是在應用程式啟動時建立,而應用程式會使用 TabbedPage。 如需詳細資訊,請參閱 殼層概觀

將 ListView 效能最佳化

使用 ListView 時,有許多應進行最佳化的使用者體驗:

  • 初始化:自控制項建立時開始,並於畫面上顯示項目時結束的時間間隔。
  • 捲動:能夠捲動清單並確保 UI 跟得上觸控手勢。
  • 互動:加入、刪除和選取項目的互動。

控制項 ListView 需要應用程式提供數據和數據格範本。 實現此目標的方式將對控制項的效能產生很大的影響。 如需詳細資訊,請參閱 快取數據

使用非同步程式設計

您可以藉由使用異步程式設計來增強應用程式的整體回應性,並經常避免效能瓶頸。 在 .NET 中,以 工作為基礎的異步模式 (TAP) 是異步操作的建議設計模式。 不過,對 TAP 的使用不正確可能會導致應用程式表現不佳。

基本項目

使用 TAP 時,應遵循下列一般指導方針:

  • 瞭解列舉所 TaskStatus 代表的工作生命週期。 如需詳細資訊,請參閱 TaskStatusTask狀態的意義。
  • Task.WhenAll使用 方法來異步等候多個異步操作完成,而不是個別await等候一系列異步操作。 如需詳細資訊,請參閱 Task.WhenAll
  • Task.WhenAny使用 方法,以異步方式等候其中一個異步操作完成。 如需詳細資訊,請參閱 Task.WhenAny
  • Task.Delay使用 方法產生Task物件,該物件會在指定時間之後完成。 這適用於輪詢數據,以及延遲處理預先決定時間的使用者輸入等案例。 如需詳細資訊,請參閱 Task.Delay
  • 使用 Task.Run 方法在線程集區上執行密集的同步 CPU 作業。 此方法是方法的 TaskFactory.StartNew 快捷方式,其中已設定最佳自變數。 如需詳細資訊,請參閱 Task.Run
  • 避免嘗試建立異步建構函式。 請改用生命週期事件或個別的初始化邏輯來正確 await 進行任何初始化。 如需詳細資訊,請參閱 blog.stephencleary.com 上的異步建構函 式。
  • 使用延遲工作模式,以避免在應用程式啟動期間等待異步操作完成。 如需詳細資訊,請參閱 AsyncLazy
  • 建立現有異步操作的工作包裝函式,其不會藉由建立 TaskCompletionSource<T> 物件來使用 TAP。 這些物件可取得可程式性的優點 Task ,並可讓您控制相關聯 Task之存留期和完成。 如需詳細資訊,請參閱 TaskCompletionSource的本質。
  • Task當不需要處理異步操作的結果時,傳回 物件,而不是傳回等候Task的物件。 由於執行的內容切換較少,因此效能更高。
  • 使用工作平行連結庫 (TPL) 數據流連結庫,例如在數據可供使用時處理數據,或當您有多個必須以異步方式通訊的作業時。 如需詳細資訊,請參閱數據流(工作平行連結庫)。

UI

使用 TAP 搭配 UI 控制件時,應該遵循下列指導方針:

  • 如果有的話,請呼叫 API 的異步版本。 這會將UI線程解除封鎖,這有助於改善應用程式的用戶體驗。

  • 使用UI線程上異步操作的數據更新UI元素,以避免擲回例外狀況。 不過,屬性的 ListView.ItemsSource 更新會自動封送處理至UI線程。 如需判斷程序代碼是否在UI線程上執行的資訊,請參閱 在UI線程上建立線程。

    重要

    透過數據系結更新的任何控件屬性都會自動封送處理至UI線程。

錯誤處理

使用 TAP 時,應該遵循下列錯誤處理指導方針:

  • 瞭解異步例外狀況處理。 執行異步的程式代碼所擲回的未處理例外狀況會傳播回呼叫線程,但在某些情況下除外。 如需詳細資訊,請參閱例外狀況處理(工作平行連結庫)。
  • 請避免建立 async void 方法,並改為建立 async Task 方法。 這些可讓錯誤處理、可組合性和可測試性更容易。 此指導方針的例外狀況是異步事件處理程式,必須傳回 void。 如需詳細資訊,請參閱 避免異步 Void
  • 請勿藉由呼叫 Task.WaitTask.ResultGetAwaiter().GetResult 方法來混合封鎖和異步程式代碼,因為它們可能會導致死結發生。 不過,如果必須違反此指導方針,慣用的方法就是呼叫 GetAwaiter().GetResult 方法,因為它會保留工作例外狀況。 如需詳細資訊,請參閱 .NET 4.5 中的異步一路和工作例外狀況處理。
  • ConfigureAwait盡可能使用 方法來建立無內容程序代碼。 無內容程式代碼對於行動應用程式有較佳的效能,而且是避免使用部分異步程式代碼基底時發生死結的實用技術。 如需詳細資訊,請參閱 設定內容
  • 使用 接續工作 來處理先前異步操作擲回的例外狀況,以及在啟動之前或執行時取消接續等功能。 如需詳細資訊,請參閱 使用連續工作鏈結工作。
  • ICommand叫用異步操作時,請使用異步ICommand實作。 這可確保可以處理異步命令邏輯中的任何例外狀況。 如需詳細資訊,請參閱 異步程式設計:異步MVVM應用程式的模式:命令

延遲建立物件的成本

延遲初始化可用來延遲物件的建立,直到第一次使用物件時。 這項技術主要用於改善效能、避免計算,以及減少記憶體需求。

請考慮針對在下列案例中建立成本高昂的物件使用延遲初始化:

  • 應用程式可能不會使用物件。
  • 在建立該物件前,有其他耗費資源的的作業需要完成。

類別 Lazy<T> 是用來定義延遲初始化的類型,如下列範例所示:

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)
{
    ...
}

延遲初始化會在第一次存取 Lazy<T>.Value 屬性時發生。 包裝於其中的類型會在第一次存取時建立及傳回,並會儲存起來供任何未來的存取使用。

如需延遲初始化的詳細資訊,請參閱延遲初始設定

發行 IDisposable 資源

IDisposable 介面提供了一個釋放資源的機制。 它提供了 Dispose 方法,應實作該方法以明確釋放資源。 IDisposable 並非一個解構函式,因此應僅在下列情況下實作:

  • 當類別擁有未受控 (Unmanaged) 資源時。 通常需要釋放的未受控資源包含檔案、串流和網路連線。
  • 當類別具有受控 IDisposable 資源時。

類型消費者接著便可以呼叫 IDisposable.Dispose 實作來在不再需要執行個體時釋放資源。 有兩種方法可以達到這個目的:

  • 藉由在 IDisposable 陳述式中包裝 using 物件。
  • 藉由將呼叫 IDisposable.Dispose 的段落包裝進 try/finally 區塊中。

在using語句中包裝IDisposable物件

下列範例示範如何在 語句中using包裝 IDisposable 物件:

public void ReadText(string filename)
{
    string text;
    using (StreamReader reader = new StreamReader(filename))
    {
        text = reader.ReadToEnd();
    }
    ...
}

StreamReader 類別實作了 IDisposable,而 using 陳述式提供了一個在 StreamReader 物件離開範圍前呼叫物件上 StreamReader.Dispose 方法的便利語法。 在 using 區塊內,StreamReader 物件是唯讀的,無法重新指派。 using 陳述式同時也會確保即使發生例外狀況時,Dispose 方法仍會獲得呼叫,因為編譯器會為 try/finally 區塊實作中繼語言 (IL)。

在 try/finally 區塊中包裝對 IDisposable.Dispose 的呼叫

下列範例示範如何將 呼叫IDisposable.Dispose包裝在 區塊中/tryfinally

public void ReadText(string filename)
{
    string text;
    StreamReader reader = null;
    try
    {
        reader = new StreamReader(filename);
        text = reader.ReadToEnd();
    }
    finally
    {
        if (reader != null)
            reader.Dispose();
    }
    ...
}

StreamReader 類別會實作 IDisposable,而 finally 區塊則會呼叫 StreamReader.Dispose 方法來釋放資源。 如需詳細資訊,請參閱 IDisposable 介面

取消訂閱事件

若要避免記憶體流失,在訂閱者遭到處置前,應先取消訂閱事件。 在取消訂閱事件之前,發行物件的事件委派都會具有一個封裝訂閱者事件處理常式委派的參考。 只要發行物件還有該參考,記憶體回收就不會回收訂閱者物件的記憶體。

下列範例示範如何取消訂閱事件:

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

Subscriber 類別在其 Dispose 方法中取消訂閱事件。

參考回收通常會在使用事件處理常式及 Lambda 語法時發生,因為 Lambda 運算式可以取得參考並使物件繼續存留。 因此,匿名方法的參考可以儲存在字段中,並用來取消訂閱事件,如下列範例所示:

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

_handler 欄位保有匿名方法的參考,並會用於訂閱事件及取消訂閱事件。

避免 iOS 和 Mac Catalyst 上的強循環參考

在某些情況下,可能會建立強式參考循環,這會防止記憶體回收行程回收物件的記憶體。 例如,假設 NSObject衍生的子類別,例如繼承自 UIView的類別,會新增至 NSObject衍生的容器,並且從 強式參考 Objective-C,如下列範例所示:

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

當此程式代碼建立 Container 實例時,C# 物件將會有對象的 Objective-C 強式參考。 同樣地, MyView 實例也會有物件的強式參考 Objective-C 。

此外,呼叫 container.AddSubview 將會增加非受控 MyView 執行個體上的參考計數。 發生這種情況時,.NET iOS 運行時間會 GCHandle 建立實例來讓 MyView Managed 程式代碼中的物件保持運作,因為不保證任何 Managed 物件都會保留其參考。 從受控程式碼的觀點來看,會在 AddSubview(UIView) 呼叫 (若不是針對 GCHandle) 之後回收 MyView 物件。

非受控 MyView 物件會有指向受控物件的 GCHandle,稱為強式連結。 受控物件將包含對 Container 執行個體的參考。 接著,Container 執行個體會有對 MyView 物件的受控參考。

在包含之物件保留其容器連結的情況下,有幾個選項可用來處理循環參考:

  • 藉由保留容器的弱式參考,以避免循環參考。
  • 呼叫物件上的 Dispose
  • 手動中斷循環,方法是將容器連結設定為 null
  • 手動從容器移除所包含的物件。

使用弱式參考

避免循環的一種方式是從子項到父項的弱式參考。 例如,上述程式代碼可能如下列範例所示:

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

在此,所包含的物件不讓父項保持運作。 不過,父系會透過對 container.AddSubView的呼叫讓子系保持運作。

這也會發生在使用委派或數據源模式的 iOS API 中,其中對等類別包含 實作。 例如,在類別DelegateUITableView設定 屬性或 DataSource 時。

在類別純粹是為了實作通訊協定而建立的情況下 (例如 IUITableViewDataSource),您可以僅實作類別中的介面並覆寫方法,然後將 DataSource 屬性指派給 this,而非建立子類別。

處置具有強式參考的物件

如果存在強式參考且難以移除該相依性,請使 Dispose 方法清除父代指標。

針對容器,請覆寫 Dispose 方法來移除包含的物件,如下列範例所示:

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

針對保留其父代強式參考的子物件,請清除 Dispose 實作中對父代的參考:

class MyChild : UIView
{
    MyContainer _container;

    public MyChild(MyContainer container)
    {
        _container = container;
    }

    public override void Dispose()
    {
        _container = null;
    }
}