しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

UI に表示されるデータの仮想化(1)

ピクセル単位で仮想化するパネル ItemsWrapGrid & ItemsStackPanel - しっぽを追いかけて では UI の仮想化についてご紹介しましたが、UI 以前にそもそもアイテムのデータ量が多い場合は表示が遅れたり、メモリを大量に消費してしまうといった問題が起こります

そこで仮想化の仕組みをデータまで広げた方法の1つが下記のようなデータのランダムアクセスによる仮想化です

GirdView や ListView などに複数のアイテムを表示する場合、表示されるデータはいったん表示用にコピーされます

このとき何もデータの仮想化を行っていないと表示されるデータのもととなるデータソースが全件コピーされます

f:id:matatabi_ux:20140802103929p:plain

しかし ItemsSource に ObservableCollection などの INotifyCollectionChanged のインターフェース実装をしたクラスか ObservableVector などの IObservableVector のインターフェース実装をしたクラスをバインドした場合、データ全件ではなく一部のみが表示用にコピーされるようになります

表示される画面のスクロール位置からコピーされるデータが絞り込まれることからこの仕組みをランダムアクセスの仮想化と呼んでいるようです

試しに 前回の記事 でも登場した TOSHIBA タブレットで下記のようなコードを試してみました

/// <summary>
/// Frame 内へナビゲートするために利用する空欄ページ。
/// </summary>
public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        this.Loaded += this.OnLoaded;
    }

    /// <summary>
    /// 画面読み込み時の処理
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        this.gridView.ItemsSource = this.GetItems();
    }

    /// <summary>
    /// 大量のアイテム生成
    /// </summary>
    /// <returns>アイテムのコレクション</returns>
    private IEnumerable<object> GetItems()
    {
        for(var i = 0; i < 70000; i++)
        {
            yield return new string('*', 1000);
        }
    }
}

こんなコードをおもむろにリモートデバッグで実行したら・・・OutOfMemoryException で落ちました!

これをほぼ同じ次のようなコードに書き換えます

/// <summary>
/// Frame 内へナビゲートするために利用する空欄ページ。
/// </summary>
public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        this.Loaded += this.OnLoaded;
    }

    /// <summary>
    /// 画面読み込み時の処理
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        this.gridView.ItemsSource = new ObservableCollection<object>(this.GetItems());
    }

    /// <summary>
    /// 大量のアイテム生成
    /// </summary>
    /// <returns>アイテムのコレクション</returns>
    private IEnumerable<object> GetItems()
    {
        for(var i = 0; i < 70000; i++)
        {
            yield return new string('*', 1000);
        }
    }
}

同じようにリモートデバッグを実行しても・・・落ちませんでした!利用されるメモリが削減されるのは確かなようです

このランダムアクセスの仮想化を利用しないとデータに必要なメモリ容量が無駄に多くなってしまうため、件数の多いデータを画面に表示する場合は ObservableCollection を利用した方がよいと思います

バインド後の動的なデータ変更を画面表示に伝搬させる目的で ObservableCollection が利用されることが多いためほとんど意識する必要はないかもしれませんが・・・