19

UITabBarControllerを備えたMonoTouchアプリがあり、各タブはUINavigationControllerです。これらの一部は、UITableViewとUIToolbarを追加するUIViewControllerをラップし、その他はDialogViewControllerをラップします。

これまでメモリ/ビュー管理にはあまり注意を払っていませんでしたが(ほとんどシミュレータで実行していました)、実際のデバイスでテストを開始すると、メモリ不足の状態が原因でいくつかの障害に気づきました(たとえば、アプリが終了し、ログから、これより前にDidReceiveMemoryWarningが呼び出されたことがわかりました)。また、GCサイクルが原因であると想定している、アプリの応答性の長時間の一時停止に気付く場合もあります。

これまでのところ、navスタックにプッシュするすべてのDialogViewControllerは、ポップしたときに割り当てられたビューやその他のものをクリーンアップすると想定してきました。しかし、私はそれがおそらくそれほど簡単ではないこと、そして物事に対してDispose()を呼び出し始める必要があることに気づき始めています。

MonoTouchとMT.Dを使用してリソースとメモリを管理する方法のベストプラクティスはありますか?具体的には:

  • ポップされた後、DialogViewControllerでDisposeを呼び出す必要がありますか?もしそうなら、これを行うのに最適な場所はどこですか?(ViewDidUnload?DidReceiveMemoryWarning?デストラクタ?)
  • DVCは、渡されたRootElementのようなオブジェクトを自動的に破棄しますか、それともこれについて心配する必要がありますか?テーブルセル(StyledStringElementなど)のレンダリングの一部としてロードされるUIImagesはどうですか?
  • GCが発生したときに応答性に少し影響を与えないように、コレクションの間隔を広げるためにGC.Collect()を呼び出す必要がある場所はありますか?
  • 世代別ガベージコレクターは対話性の問題に役立ちますか?また、本番アプリで使用するのに十分安定していますか?(MonoDevelop 3.0.2 / MT 4.3.3ではまだ「実験的」として請求されていると思います)
  • iOSが私のアプリを撃つ可能性を減らすために、DidReceiveMemoryWarningで何をする必要がありますか?非表示の各ViewControllerがこの呼び出しを受け取るように見えるので、そのView Controllerのリソースをクリーンアップする必要があると想定しています...ViewDidUnloadで行うのと同じ種類のことを行う必要がありますか?
  • ViewDidUnloadが呼び出されないようです(DidReceiveMemoryWarningを取得した後でも)。実際、ログでそれを見たことはありません。iOSがDidReceiveMemoryWarningの後に常にViewDidUnloadを呼び出した場合、ViewDidUnloadですべてのクリーンアップを実行できます...クリーンアップの責任をViewDidUnloadとDidReceiveMemoryWarningの間で分割する最良の方法は何ですか?

この質問の一般的な性質についてお詫びします-これはホワイトペーパーにとって良いトピックのようですが、何も見つかりませんでした...

更新:質問をより具体的にするために:InstrumentsとXamarin Heapshotプロファイラーを使用した後、ユーザーがナビゲーションスタックをポップしたときにUIViewControllersがリークしていることは明らかです。Rolfがバグを報告しましたこれと2つの重複があるので、これは私だけではなく本当の問題です。残念ながら、リークされたUIViewControllerの適切な回避策は見つかりませんでした。Dispose()を呼び出すのに適した場所が見つかりませんでした。ViewDidLoadによって割り当てられたリソースを解放する自然な場所は、ViewDidUnloadメッセージにありますが、シミュレーターで呼び出されることはないため、メモリーフットプリントは増え続けます。デバイスにはDidReceiveMemoryWarningが表示されますが、iOSが実際にビューをアンロードする保証はなく、ViewDidLoadが再度呼び出される保証もないため、ViewControllerとそのリソースを解放する場所としてこれを使用することには消極的です。いずれか(基になるリソースが破棄された状況に対して防御的にコーディングする必要があるViewDidAppearにつながります)。私'

4

1 に答える 1

30

MT.Dソースコードとプロファイラーで数日過ごしました。DidReceiveMemoryWarningとViewDidUnloadを実装するための最良のデザインパターンについての一般的なガイダンスをまだ探していますが、誰かに役立つ可能性のあるいくつかの一般的な所見を共有しています。

  1. MonoTouch.Dialogは非常に適切に動作します。通常の使用ではリソースをリークしません。コントロールツリーをDVC.Rootの下に保持し、各要素のDisposeメソッドが基になるUIKitコントロールを正しく破棄します。DVC.Rootを置き換えた場合は、古いRootElementを破棄することを心配する必要はありません。プロパティセッターが自動的に破棄します。全体として、MT.Dは重大なメモリの問題に悩まされているようには見えません。例外が1つあります-以下を参照してください。
  2. 独自のカスタム要素(MultilineEntryElementなど)を作成するときは、必ずDispose(bool)メソッドをオーバーライドし、基になるUIKitコントロール(UITextViewなど)を破棄して、基本クラスのDispose()メソッドをチェーンしてください。MiguelのMT.Dgithubプロジェクトのソースコードには、多くの良い例があります。すべての要素は、標準のDisposeパターンを実装しています(ただし、Dispose(false)を呼び出すデストラクタ/ファイナライザは省略されています)。
  3. カスタムビューコントローラーを実装する場合、通常、UIViewControllerサブクラスにも、TableViewDataSourceまたはDelegateクラスにもDisposeを実装する必要はありません。ビューコントローラがGCされると、参照に対してDisposeが正しく呼び出されます。データソースで割り当てたすべてのセルは適切に廃棄されます。
  4. (3)の例外として-TableViewのセルに自分のサブビューを追加するときに厄介な問題が発生しました。このサブビューは、「UICheckbox」と呼ばれる私が作成したコントロールであり、最終的にUIImageViewから継承します。UIImageViewには、2つのUIImage(オンとオフ)とClickedと呼ばれるパブリックイベントがあります。DataSourceのメンバーを参照するイベントハンドラーがこのイベントにフックされている場合にのみ問題が発生します(イベントハンドラーがDataSourceまたはコントローラー自体を参照しない場合は、すべて問題ありません)。ただし、上記の条件が満たされ、コントローラーが閉じられると、GCが理解できないサイクルが明らかに発生し、TableViewに配置したすべてのUICheckboxが(その画像とともに)リークされます。これを回避する唯一の方法は、ViewDidDisappearにコードを追加して、ViewControllerを破棄し、ナビゲーションスタックのどこにも存在しない状態IFFをクリーンアップすることでした。それはハッキーですが、それは動作します。
  5. 一般に、View Controllerでオブジェクトを割り当てるために、次のテンプレートを順守します。

    • コンストラクターには何も割り当てません(状態を渡すためにのみ使用します)
    • ViewDidLoadでコントロールツリーを作成します(そしてViewDidUnloadで破棄します)。XAMLの「InitializeComponent」を考えてください(それが役立つ場合)。UIViewControllerがDialogViewControllerをnavスタックにプッシュする場合、ViewDidLoadはDVCを作成するのに適した場所です。
    • ViewDidAppearのコントロールツリーの値を初期化します。たとえば、このメソッドでは、要素、セクション、さらにはDVCのルートを追加/削除/置換できます。ただし、新しいDVCを作成しないでください。
  6. ユーザーがナビゲーションスタックを上に移動すると、ViewControllerがリークするという一般的な問題があります(質問の「更新」にあるbugzillaリンクを参照しています)。これはMT.Dにも影響します。かなり簡単な回避策があります-親ViewControllerのViewDidAppearに次のコード行を追加します。

        // HACK: touch the ViewControllers array to refresh it (in case the user popped the nav stack)
        // this is to work around a bug in monotouch (https://bugzilla.xamarin.com/show_bug.cgi?id=1889)
        // where the UINavigationController leaks UIViewControllers when the user pops the nav stack
        int count = this.NavigationController.ViewControllers.Length;
    

Rolfは、このバグが発生する理由と、bugzillaリンクで回避策が機能する理由を説明する素晴らしい仕事をしているので、繰り返しません。

誰かがこれがお役に立てば幸いです。また、私より賢い人が、DidReceiveMemoryWarningを処理する方法と、そのメソッドとViewDidUnloadの間で作業を分割する方法についてのガイダンスを持っていることを願っています。

更新:さらにいくつかのメモ:

  • DidReceiveMemoryWarningとViewDidUnloadのプロトコルを理解しました。前者は常にすべてのViewControllerに配信され、後者は現在表示されておらず、ナビゲーションスタックのルートよりも深くないViewControllerにのみ送信されます。結局、キャッシュしてダンプできる画像が実際にはないため、DidReceiveMemoryWarningを無視することにしました(iOSガイダンスに従って)。ViewDidUnloadで、ViewDidLoadで割り当てたすべてのリソースを解放します。
  • 私のアプリにはTabBarがあり、各タブはUINavigationControllerをホストし、そのほとんどがDialogViewControllerをプッシュします。私が扱っていた問題の1つは、ViewDidUnloadがDialogViewControllerへの参照を解放した後にDialogViewControllerがリークすることでした。ViewDidUnloadでDVCを破棄しようとしましたが、iOSはそれを再度呼び出したいと思っていたため、GCされたオブジェクトでセレクターを呼び出す例外が発生していました。私はその理由を発見しました-ナビゲーションコントローラーがViewControllers配列のDVCを保持していました。解決策は、その場所に長さゼロの配列を作成して配列を解放することです-ViewDidUnloadで:

    this.ViewControllers = new UIViewController[0];
    

古いアレイはGCされ、DVCもGCされます。これは、それを指しているものがなくなったためです。また、iOSがオブジェクトを再起動することはありません。注-DVCでDisposeを呼び出す必要はありません。

于 2012-06-14T05:51:54.790 に答える