RIAアーキテクチャ研究会 第4回セミナー 尾上 雅則 自己紹介 尾上 雅則 (おのうえ まさのり) 昭和58年生。フリーランス Blog – the sea of fertility http://ugaya40.net Twitter - @ugaya40 Facebook – http://facebook.com/ugaya40 C#er/MVVMer Microsot MVP for Visual C#(2012/7~) Agenda WPF MVVM開発の悩みどころ Livetの概要 Livetのライフサイクル管理 冗長で面倒なコードを倒す Livetのメッセージング機能 Livetのその他の機能 まとめ - Livetの未来 WPF MVVM開発の悩み処 MVVM という考え方 – 概要 Model – View – ViewModelパターン WPFをはじめとするXAML系プラットフォームのため に生まれたMVC系パターン PresentationDomainSeparationを目的とする 現在はXAML系のみならずAndroidやブラウザ Javascriptの世界でも利用される MVCなどと比べて優劣があるものではなく、適する プラットフォームと適さないプラットフォームがある のみであることに注意 MVVM という考え方 – 各責務 Viewの責務 XAMLとXAML関連コードで賄える事(表示など) XAMLのための状態ストア ViewとViewModel以外の部分(XAMLプラットフォームが関連 ない部分) ViewModelの責務 Modelの責務 詳細はこちら http://ugaya40.net/architecture/20120804wankuma.html 問題① – メモリリーク MVVMに限らず、Observaerパターンを使用するス テートフルなリッチクライアントのための責務分割型 設計パターンではWebシステムでは発生しにくいイベ ント関連のメモリリークが非常に発生しやすい。 .NETにおけるイベントの仕組みと参照の関係 責務分割した際のライフサイクル C#でのイベントハンドリングと解除 イベントの仕組み - 問題① || .NETではイベント発行側がイベント受信側の参照を保 持している。つまりイベント受信側はイベントの購読 を解除してからじゃないと勝手に消滅できない。 イベントの購読を解除せずに、受信側オブジェクトを 破棄したつもりだと実際には受信側オブジェクトは消 滅せずメモリリークとなる。 責務分割型のライフサイクル – 問題① ViewとViewModelは基本データバインド(内部で WeakEventが使用されメモリリークしない) Model(ViewとViewModel以外の部分)はアプリケー ションの本質にかかわる部分なのでずっと消滅しない ことが多い。つまりViewModelより長生き。 責務分割型のライフサイクル – 問題① < 長生き イベント監視 参照保持 イベントハンドラの解除をかなり正確にやらないとリ スキーなのはわかりますよね? C#でのイベント - 問題① C#でのイベントハンドリング、どう書きますか? では解除はこうですか? これでは解除できません。残念ながらラムダで直接イベント ハンドラを書いた時点でイベントを解除する手段はなくなり ます。 C#でのイベント - 問題① イベントハンドリングとその解除を適切に行うにはこ うします。 しかしイベントハンドラの登録と解除を同じメソッド 内で行うことは稀有です。つまり・・ C#でのイベント - 問題① ハンドラとイベント発行オブジェクトでフィールド管理が地 獄に!!! 問題① – メモリリーク まとめ MVVMパターンではイベント受信側オブジェクトの方 が基本的に寿命が短く、メモリリークのリスクが大き い。 そのため適切にイベントハンドラの登録と解除を管理 する必要がある。 C#でのイベントハンドラの登録と解除が複数メソッ ドにまたがる場合(その方が多い)、クラスフィールド でイベントハンドリング関連オブジェクトが増えやす くつらい。 問題②–バインドできないプロパティ MVVMパターンのViewModelはViewの状態ストアで す。XAMLはバインディングファーストなDSLである ため、Viewは基本的にViewModelのプロパティをバ インドで表示する形でレンダリングされます。 ViewとViewModel間の通信は極力データバインディ ングで統一されるほど問題が発生しにくくなっていま す。 データバインディングを使用するとViewはViewModel の変更通知イベントを監視しますが、データバインディ ング機構内部ではWeakEvent(弱いイベント)という機 構が使用されているため、メモリリークが発生しません。 単純に責務感通信方法が統一されるほど管理は容易 問題②–バインドできないプロパティ バインドできない! 依存関係プロパティとして用意されているもの以外は バインドできません。それが結構多いんです・・。 問題③–冗長なコード1 バインドするために必須な 変更通知プロパティ。 冗長なコードの代表格です ね。面倒です。 (ViewModel & Model) 問題③–冗長なコード2 操作をバインドするため のICommand実装。こ れも冗長です。 (ViewModel) WPF MVVM開発の悩み処 – まとめ ① メモリリークが発生しやすい & きちんとした管理が難し いのでイライラする ② バインドできないプロパティにイライラする ③ 冗長なコードにイライラする → 要はイライラする。 ※一番上のもの以外はMVVMのせいではなくWPF(というか XAML系全般)自体の問題であることに注意 Livetはこれらに対応するソリューションです。 Livetの概要 Livetとは? WPF4のための「国産」MVVMインフラストラクチャ ライセンスはzlib/libpng 通常の使用であれば、クレジットなど必要ない ソース改変使用する場合はクレジットの明記が必要 Project Home http://ugaya40.net/Livet GitHub https://github.com/ugaya40/Livet Livetとは? 来月(2012/10)で開発開始から2年 採用実績は企業・個人開発者ともに多数 おそらくは日本一のC#erたちが集うTLにて多くの方 からフィードバックをいただき、議論を積み重ねて 作ってきました MVVMの概念そのものを突き詰めた結果のシナリオを ベースに機能を構築しており、ほかのMVVMライブラ リが未着手な問題の多くに着手できています Livetのインストール Visual Studio 2010/2012なら拡張機能マネージャ から一発導入 Visual Studio 2010 Express C#/Visual Basic ある いはVisual Studio 2012 Express for Windows DesktopならSetup.exeから一発導入 ライブラリだけならNugetからも導入可能です Livetの構成 Visual Studio2010/2012/Express各環境用、 C#/VisualBasic双方用のプロジェクトテンプレー ト・アイテムテンプレート・コードスニペットなどが 含まれます。 Livetのライフサイクル管理 Livetのライフサイクル管理 Livet0.99系までは、Model⇔ViewModel間のライフ サイクル管理にWeakEventを使用してメモリリーク を防いでいました。 しかし、WeakEventはCPUが回りすぎるという フィードバックをいくつかいただいたため、また ViewModelとModelの間はView⇔ViewModelの間ほ どライフサイクル管理が難しくはないため、現在の Livet1.0系ではすべて手動管理する形になっています。 ※View⇔ViewModel間は極力データバインディングによ る通信を行う事で、WeakEventが自動的に使用されます Livetのライフサイクル管理 メモリリークの問題、このコードをLivetはどう変え るのでしょうか? Livetのライフサイクル管理 ViewModelではこうなります。ともにLivetの機能で あるPropertyChangedEventListenerと CompositeDisposableが鍵です。 PropertyChangedEventListener PropertyChangedEventListenerとは、 PropertyChangedイベント購読状態をIDisposable として扱うための機能です。Disposeによってイベン ト購読が解除されます。 また、普通にイベントハン ドラでは変更通知が来たプ ロパティ名ごとにこうなり がちなところが・・ PropertyChangedEventListener あるいはコレクション初期化子を用いて・・ こう書けたりします。 LivetCompositeDisposable LivetのViewModel型には最初からメンバとし て定義されているIDisposableのコレクション です。CompositeDisposableをDisposeするこ とでコレクションメンバのIDisposableもすべ てDisposeされます。 LivetのViewModel(IDisposable)型では ViewModelがDisposeされる際に CompositeDisposableがDisposeされます。 LivetCompositeDisposable つまりこう記述するだけで、listenerのイベン ト購読もViewModel破棄時にすべてDisposeさ れるわけです。 ViewModelのDisposeは誰が呼ぶのか? LivetのWindowテンプレートではWindowを閉じる 際に、DataContextがIDisposableであれば Disposeを行うビヘイビアが最初から記述されてい ます。 WPFのViewModelのオブジェクト階層は結局常 にWindowのDataContextがルートにいます。 メンバに子ViewModelが存在するViewModelも、 その子ViewModelを自身の CompositeDisposableに放り込むことで簡単に ハンドラの解除が管理できるわけです。 ViewModelのDisposeは誰が呼ぶのか? Windowが閉じられるとす べてのイベントハンドラ や破棄可能なリソースが 連鎖的に破棄されます Livet - ViewModelのライフサイクル管理 LivetではPropertyChangedEventListener以外にも、 CollectonChangedEventListener、ユーザー定義イ ベントに対応するための汎用クラスEventListenerク ラスが定義されています。 LivetのViewModelでは用意されている CompositeDisposableや各種EventListenerを使用し てViewModelのライフサイクル管理が容易に行える ようになっています。 ViewとViewModelのライフサイクル管理 データバインドでの通信はWeakEvent機構が使用さ れ、メモリリークは発生しえませんが、データバイン ドでは処理できないイベントが残ります。 次章で説明するメッセージング機構を使えば、 ViewModelからのイベントをデータバインディング で扱えますが、ビヘイビア・トリガー・アクションな どの知識も必要ですし、そういったコードビハインド に依存しない実装はView抽象化要件がなければ意義 は薄いものです。 ViewとViewModelのライフサイクル管理 Model⇔ViewModel間と異なりView⇔ViewModel間 のハンドラの管理は難易度が高いため(Viewの生成消 滅の頻繁さ・WPF自体バグ起因・部品コンポーネン ト化による複雑さ)自分でハンドラの管理をしっかり できれば一番ですが、不安な場合は LivetWeakEventListenerを使用することができます。 LivetWeakEventListenerは汎用WeakEvent機構で す。.NET標準のものより手軽に、しかもはるかに高 速に(実測4~5倍)WeakEvent機構を使用できます。 LivetWeakEventListener 冗長で面倒なコードを倒す コードスニペット - デモ Livetでは変更通知プロパティ・ICommand2種のコード スニペットが用意されています。 lprop/lpropn それぞれ.NET4/4.5用の変更通知プロパティ lvcom/lvcomn ReSharper7のINotifyPropertyChangedサポートにも対応し ています。 引数を取らないICommand実装クラスViewModelCommand のフル・CanExecute無し版 llcom/llcomn 引数を取るICommand実装クラスListenerCommandのフ ル・CanExecute無し版 メソッド直接バインディング - デモ Livetを使用すると実はICommandを全く使わずに ViewModelからViewに操作を公開することも可能で す。 LivetのView機能はすべてICommand無しのメソッ ド直接バインディングに対応しています 内部ではリフレクション・式木・Taskなどをフル活 用したメソッドキャッシュ機構があり、またそれらメ ソッドキャッシュ生成機構はMethodBinderというク ラスにまとめてあり、開発者が自身のクラスに簡単に メソッド直接バインディングの仕組みを追加すること もできます。 メソッド直接バインディング LivetCallMethodActionはBlend SDKの CallMethodAcitonより高速で、かつ引数をとること もできます。 後述するメッセージング機構でもメソッド直接バイン ディングは使用できるので後でまた確認することにし ます。 Livetのメッセージング機能 メッセージング機構とは? ViewModelからViewに操作指示メッセージを送るた めの機構です。 ViewModelから既定のメンバであるMessengerを使 用してViewにメッセージを送信します。 現在のLivetでは、XAML上に記述する InteractionMessageTriggerでMessengerからの メッセージを受け、対応する処理となるアクションを 実行します。 View抽象化要件がなければ、しいてメッセージング 機構に頼る必要はありません! 2種類のメッセージ対応アクションの起動-デモ ViewModelからInteractionMessageTriggerを介し ての起動 ViewModelのMessenger + InteractionMessageTriggerによるアクションの起動 Viewから直接の起動 EventTriggerなどの任意のトリガーによるアクション の起動 Livetのメッセージング機能まとめ ご覧のとおり、Livetのメッセージ機能はすべてメ ソッド直接バインディングが使用でき、かつView起 点・ViewModel起点でアクションを起動できます。 メッセージング機構はイベントをデータバインディン グで処理するような機構であり、その内部ではデータ バインディングと同じくWeakEventが使われていま す。 View抽象化要件がないシナリオでは特にこだわる必 要はありません。 そのほかのLivet機能 ViewModelHelper. CreateReadOnlyDispatcherCollection Modelの変更通知コレクションに自動的に連動する ViewModelのコレクションを作製します。 戻りであるReadOnlyDispatcherCollectionはIDisposable であり、DisposeすることでModelコレクションとの連動 が解除され、自身の各要素がIDisposableである場合すべ てDisposeします。 簡単にModelコレクションからViewModelのコレクション を生成でき、CompositeDisposableと組み合わせること で管理も容易です。 バインドできないプロパティ対策 Livetではバインドできないプロパティの単一方向バインドを可 能にするビヘイビアとアクションが大量に含まれています。 バインドシステムの制約から、2種類ともつけて双方向と いうやり方はできません。 ビヘイビア・アクション・トリガーなど LivetDataTrigger Blend SDKのそれと違い、初期値に対応した DataTrigger WindowCloseCancelBehavior Windowのクローズ可否処理をViewModelなどバインド 先に委譲できるビヘイビア SetFocusAction アタッチしたコントロールにフォーカスを移動する など その他 ObservableSyncronizedCollection スレッドセーフなObservableCollection 汎用EnumToBooleanConverter Enumとboolの相互変換を行いIValueConveterの実相 の手間を削減する アタッチしたコントロールにフォーカスを移動する など Livetの未来 - まとめ Livetの今後の予定 Livet英語化プロジェクト コードビハインド実装の補完 より簡潔なViewModelイベントのコードビハインドで のハンドリングなど バインドできないプロパティ対策の高度化 バインドできないプロパティの中からシナリオベースで よく使われる機能を双方向バインド可能にする (すでにTextBoxとPasswordBoxはある) Livet - まとめ LivetはMVVMについて適切に理解していれば適切に 理解しているほどその機能がいかせるライブラリです。 ほかのMVVMライブラリと異なりViewModel単体で はなく全体を見たライブラリになっています。 プロジェクトテンプレート・スニペット・ハンドラ管 理・メソッド直接バインディングなどの機能をつかっ ていただければ2度とほかのMVVMライブラリには戻 れないものになっていると思います。 ご清聴ありがとうございまいた!
© Copyright 2025 ExpyDoc