2012-06-12 32 views
19

我有一个具有UITabBarController的MonoTouch应用程序,每个选项卡都是一个UINavigationController。其中一些包装了一个UIViewController,它添加了一个UITableView和一个UIToolbar,其他包装了一个DialogViewController。我到目前为止还没有关注内存/视图管理(我一直主要在模拟器中运行),但由于我已经开始在真实设备上进行测试,所以我注意到由于低内存条件(例如,应用程序被终止,并且我从我的日志中发现DidReceiveMemoryWarning在此之前被调用)。其他时候,我注意到我假设的应用程序响应时间延长是由于GC循环造成的。使用MonoTouch和MonoTouch.Dialog进行内存/资源管理

到目前为止,我一直假设每一个我推入导航堆栈的DialogViewController都会清理它的视图以及在弹出时分配的其他东西。但是我开始意识到它可能并不那么容易,而且我需要开始对事物调用Dispose()。

是否有最佳做法来处理如何使用MonoTouch和MTD管理资源和内存?具体如下:

  • 是否需要在DialogViewController弹出后调用Dispose?如果是这样,那么最好在哪里做? (ViewDidUnload?DidReceiveMemoryWarning?析构函数?)
  • DVC是否自动处理传递给它的RootElement之类的对象,还是我需要担心这个问题?作为渲染表格单元(例如StyledStringElement)的一部分加载的UIImage如何?
  • 有些地方我应该调用GC.Collect()以便更好地分配集合,以便在GC发生时不会在响应中产生一些影响?
  • 世代垃圾回收器是否有助于解决交互问题,并且足够稳定以用于生产应用程序? (我相信在MonoDevelop 3.0.2/MT 4.3.3中它仍然是“实验性”)
  • 我需要在DidReceiveMemoryWarning中做些什么来降低iOS拍摄我的应用程序的可能性?由于每个不可见的视图控制器似乎得到这个调用,我假设我应该清理该视图控制器的资源...我应该做同样的事情,我在ViewDidUnload做什么?
  • 我似乎没有让我的ViewDidUnload调用(即使我得到DidReceiveMemoryWarning后)。事实上,我不记得在我的日志中看到过它。如果iOS总是在DidReceiveMemoryWarning之后调用我的ViewDidUnload,那么我只需要在ViewDidUnload中执行所有的清理操作...在ViewDidUnload和DidReceiveMemoryWarning之间拆分清理责任的最佳方式是什么?

我对这个问题的一般性质的道歉 - 这似乎是一个白皮书一个很好的话题,但我找不到任何...

更新:使问题更具体:在使用Instruments和Xamarin Heapshot分析器之后,当用户弹出导航堆栈时,很明显我泄漏了UIViewControllers。 Rolf为此提交了一份bug,它有两个孪生兄弟,所以这对我来说不仅仅是一个真正的问题。不幸的是,我还没有找到一个很好的解决办法,泄漏UIViewControllers - 我还没有找到一个好的地方调用他们的Dispose()。 ViewDidLoad分配资源的自然空间位于ViewDidUnload消息中,但它永远不会在模拟器上调用,因此我的内存占用量不断增长。在设备上,我看到了DidReceiveMemoryWarning,但我不愿意将它用作释放视图控制器及其资源的地方,因为我无法保证iOS实际上会卸载视图,因此不能保证我的ViewDidLoad会再次被调用(导致ViewDidAppear需要对其底层资源处置情况进行防御编码)。我很想得到一些关于如何摆脱这种混乱的建议...

回答

30

我已经在MT.D源代码和分析器中花了几天时间。虽然我仍然在寻找什么最好的设计模式是实现DidReceiveMemoryWarning和ViewDidUnload一般性指导,我有一些一般性意见,分享,可能是有用的人:

  1. MonoTouch.Dialog是很好的表现。它不会在普通使用情况下泄漏任何资源。它在DVC.Root下保留一个控制树,并且每个元素的Dispose方法都正确放置底层的UIKit控件。如果您替换了DVC.Root,您甚至不必担心丢弃旧的RootElement - 属性设置器会自动为您处置它。总的来说,MT.D似乎没有任何重大的内存问题。有一个例外 - 见下文。
  2. 创建自己的自定义元素(例如MultilineEntryElement)时,请确保覆盖Dispose(bool)方法,处理底层UIKit控件(例如UITextView)并链接基类Dispose()方法。 Miguel的MT.D github项目中的源代码提供了很多很好的例子。所有元素实现标准的Dispose模式(尽管它们省略了调用Dispose(false)的析构函数/终结器)。
  3. 实现自定义视图控制器时,通常不需要在UIViewController子类上实现Dispose,也不必在TableView DataSource或Delegate类上实现Dispose。当视图控制器获取GC时,它会正确调用Dispose的引用。您在DataSource中分配的所有单元格都会正确放置。
  4. 作为(3)的例外 - 当我将自己的子视图添加到TableView的单元格时,我遇到了一个讨厌的问题。这个子视图是我创建的一个名为“UICheckbox”的控件,它最终从UIImageView继承而来,UIImageView有两个UIImages(on和off)和一个名为Clicked的公共事件。我只遇到引用DataSource成员的事件处理程序挂钩到此事件(如果事件处理程序未引用DataSource或控制器本身,一切正常)时遇到问题。但是,当满足上述条件并且控制器被解雇时,GC显然有一些周期是无法弄清楚的,并且每个放在TableView上的UICheckbox都会泄漏(连同它的图像)。我发现解决这个问题的唯一方法是向ViewDidDisappear添加代码以处置ViewController并清除其状态IFF,它不再位于导航堆栈中的任何位置。这是hacky,但它的作品。
  5. 在一般情况下,我坚持下面的模板在我的视图控制器分配对象:

    • 在构造函数(仅使用它在传递状态)
    • 创建viewDidLoad中控制树分配什么(并将其置于ViewDidUnload中)。在XAML中考虑“InitializeComponent”(如果有帮助的话)。如果UIViewController要将DialogViewController推送到导航堆栈上,那么ViewDidLoad是创建DVC的好地方。
    • 在ViewDidAppear的控制树中初始化值。例如。您可以使用此方法添加/删除/替换元素,节,甚至是DVC的根。但不要创建一个新的DVC。
  6. 当用户导航导航堆栈时(我参考问题中的“更新”中的bugzilla链接),有一个普遍的问题出现ViewController泄漏。这也影响MT.D.有一个非常简单的解决办法 - 添加以下代码行中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; 
    

罗尔夫做了伟大的工作,解释为什么这个错误发生,为什么解决方法工作在Bugzilla的链接,所以我不会重复。

我希望有人认为这有用。我也希望比我聪明的人有一些关于如何处理DidReceiveMemoryWarning的指导,以及如何在该方法和ViewDidUnload之间分割工作。

更新: 一对夫妇更注意事项:

  • 我现在意识到了DidReceiveMemoryWarning和ViewDidUnload协议:前者总是传递到每一个视图控制器,而后者只对视图控制器发送的目前没有显示,并且不比导航堆栈的根深。最后,我决定忽略DidReceiveMemoryWarning,因为我没有真正拥有缓存并可以转储的图像(根据iOS指南)。在ViewDidUnload中,我释放了在ViewDidLoad中分配的所有资源。
  • 我的应用程序有一个TabBar,其中每个选项卡托管一个UINavigationController,其中大部分都会推送一个DialogViewController。我正在处理的一个问题是在ViewDidUnload放弃引用后泄漏DialogViewController。我试着在ViewDidUnload中放置DVC,但是iOS一直想要重新调用它,并且我得到了一个异常来调用GC对象上的选择器。我发现原因 - 导航控制器在ViewControllers数组中保留DVC。该解决方案是在其位置创建零长度数组释放阵列 - 在ViewDidUnload:

    this.ViewControllers = new UIViewController[0]; 
    

旧的阵列现在将GC'ed,因此将DVC因为没有指向到了它了。而iOS不会重新激活这个对象。注 - 不需要在DVC上调用Dispose。

+6

这是一个很多有价值的信息,谢谢你把它放在一起。给未来的读者一个提示:从iOS 6开始,系统将不会调用ViewDidUnload。 –