你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> CollectionView導致內存洩露?

CollectionView導致內存洩露?

編輯:IOS開發綜合
本文將創建一個示例項目,運行後探查內存,發現本應被垃圾回收的UI控件沒有被回收。進一步發現是CollectionView導致控件不能被回收。最後,通過查看.NET Framework源代碼,發現其實不是內存洩露,虛驚一場。 發現問題 創建一個用戶控件GroupControl,有AddGroup(object header, object[] items)方法。這個方法就是創建一個GroupBox,設置Header和GroupBox裡面的ListBox.ItemsSource。 GroupControl.xaml [html]   <ContentControl x:Class="Gqqnbig.TranscendentKill.GroupControl"                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"                 mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">       <ItemsControl Name="selectionGroupPanel" x:FieldModifier="private" HorizontalAlignment="Left" VerticalAlignment="Top"/>   </ContentControl>     GroupControl.xaml.cs [csharp]  public partial class GroupControl   {       public GroupControl()       {           InitializeComponent();       }          public event SelectionChangedEventHandler SelectionChanged;          public void AddGroup(object header, object[] items)       {           GroupBox groupBox = new GroupBox();           groupBox.Header = header;           ListBox listBox = new ListBox();           listBox.ItemsSource = items;           listBox.SelectionChanged += listBox_SelectionChanged;           groupBox.Content = listBox;              selectionGroupPanel.Items.Add(groupBox);       }          void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)       {           if (SelectionChanged != null)               SelectionChanged(this, e);       }   }     然後主窗口使用這個GroupControl,在窗口加載的時候往GroupControl裡填數據,當用戶選擇GroupControl裡任意一項的時候,卸載這個GroupControl。 MainWindow.xaml [html]   <Window x:Class="Gqqnbig.TranscendentKill.UI.MainWindow"           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"           Width="800" x:ClassModifier="internal" Loaded="Window_Loaded_1">      </Window>     MainWindow.xaml.cs [csharp]  internal partial class MainWindow : Window   {       public MainWindow()       {           InitializeComponent();       }             private void Window_Loaded_1(object sender, RoutedEventArgs e)       {           Tuple<string, object[]>[] cps = new Tuple<string, object[]>[2];           cps[0] = new Tuple<string, object[]>("時間", new[] { (object)DateTime.Now.ToShortTimeString() });           cps[1] = new Tuple<string, object[]>("日期", new[] { (object)DateTime.Now.ToShortDateString() });                 GroupControl win = new GroupControl();              for (int i = 0; i < cps.Length; i++)           {               ContentPresenter[] items = new ContentPresenter[cps[i].Item2.Length];                  for (int j = 0; j < cps[i].Item2.Length; j++)                   items[j] = new ContentPresenter { Content = cps[i].Item2[j] };                  win.AddGroup(cps[i].Item1, items);           }              win.SelectionChanged += win_SelectionChanged;           Content = win;       }          void win_SelectionChanged(object sender, SelectionChangedEventArgs e)       {           GroupControl win = (GroupControl)this.Content;           win.SelectionChanged -= win_SelectionChanged;              Content = null;                 GC.Collect();       }   }     當卸載了GroupControl之後,盡管也調用了GC,我用.NET Memory Profiler查看,發現它還是存在。 圖1   圖2 圖2表示GroupBox._contextStorage保存了我的GroupControl;ListBox._parent保存了前面的GroupBox; ItemsPresenter保存了前面的ListBox;以此類推。因為有對GroupControl的引用鏈存在,所以它無法被垃圾回收。 不徹底的解決方案 從引用鏈可以發現,ContentPresenter引用了父元素ListBoxItem,所以嘗試從ContentPresenter入手。不生成ContentPresenter,直接用原始的集合。 把MainWindow.cs的 [csharp]   for (int i = 0; i < cps.Length; i++)   {       ContentPresenter[] items = new ContentPresenter[cps[i].Item2.Length];          for (int j = 0; j < cps[i].Item2.Length; j++)           items[j] = new ContentPresenter { Content = cps[i].Item2[j] };          win.AddGroup(cps[i].Item1, items);   }   改為 [csharp]   for (int i = 0; i < cps.Length; i++)   {       //ContentPresenter[] items = new ContentPresenter[cps[i].Item2.Length];          //for (int j = 0; j < cps[i].Item2.Length; j++)       //    items[j] = new ContentPresenter { Content = cps[i].Item2[j] };          win.AddGroup(cps[i].Item1, cps[i].Item2);   }   。這樣探查內存,發現GroupControl消失了。問題疑似解決。   但這樣的解決方案留給人幾點疑惑: 控件之間的相互引用不應阻礙垃圾回收,只要它們沒有被外部的長生命周期的實例引用。 這個解決方案似乎不能得出什麼一般性的原則來避免疑似由ContentPresenter引起的內存洩露。眾所周知,WPF裡大量使用ContentPresenter,難道都會洩露?   仔細查看內存探查的結果(圖3),會發現ListCollectionView(也存在於圖2中的第7行)並沒有被垃圾回收。 圖3 所以,需要一個更徹底的解決方案。 尋找原因/徹底的解決方案 圖3說明ListCollectionView跟外部做了什麼交互,導致自己被引用上了,所以一連串都不能被回收。 我在VS裡輸入ListCollectionView,然後按F12轉到定義。我裝了Resharper,做過查看.net源代碼的配置,所以就可以轉到ListCollectionView的源代碼。可是不知為什麼,ListCollectionView的代碼是空的。於是我轉到CollectionView。 www.2cto.com 然後查找哪裡使用了CollectionView。Reshaper 7.0疑似跟以前相比改進過了,可以查找.net類庫裡使用的類,於是我找到了ViewTable.Purge(),而且ViewTable也正好在引用鏈裡面。 圖4 查找ViewTable.Purge()的調用方,可以找到ViewManager.Purge()。繼續查找,定位到 DataBindEngine.GetViewRecord(object collection, CollectionViewSource key, Type collectionViewType, bool createView, Func<object, object> GetSourceItem):ViewRecord。所以差不多明白了,每當創建新的CollectionView的時候,就會調用Purge,就會刪除未使用的舊的CollectionView。 於是嘗試解決辦法,在MainWindow.cs的GC.Collect()之後,加上 [csharp]   ListBox listBox = new ListBox();   int[] n = { 1, 2, 3, 4 };   listBox.ItemsSource = n;   Content = listBox;     再探查內存,發現只有一個ListCollectionView,值為1234;而舊的“時間”“日期”的CollectionView已經被銷毀了。 經過此番研究,結論是移除對ItemsControl的引用後,ItemsControl的CollectionView不會銷毀,ItemsControl本身可能也不會銷毀。如果再創建一個新的ItemsControl,並填充數據,舊的CollectionView和ItemsControl就會被銷毀了。
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved