ios - 使用MonoTouch和MonoTouch.Dialog进行内存/资源管理

标签 ios memory-management xamarin.ios monotouch.dialog

我有一个具有UITabBarController的MonoTouch应用程序,每个选项卡都是一个UINavigationController。其中一些包装了一个UIViewController,后者添加了一个UITableView和一个UIToolbar,其他包装了DialogViewController。

到目前为止,我并未对内存/ View 管理给予太多关注(我主要在模拟器中运行),但是当我开始在真实设备上进行测试时,由于内存不足(由于内存不足而导致的一些失败)(例如,应用程序终止,并且从我的日志中发现在此之前已调用了DidReceiveMemoryWarning)。其他时候,我发现我认为应用程序的响应时间较长的暂停是由于GC周期造成的。

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

是否存在有关如何使用MonoTouch和MT.D处理资源和内存的最佳实践?具体来说:

  • 是否需要在DialogViewController弹出后调用Dispose?如果是这样,最好在哪里做? (ViewDidUnload?DidReceiveMemoryWarning?析构函数?)
  • DVC是否会自动处理传递给它的RootElement之类的对象,还是我需要为此担心?在呈现表格单元格(例如StyledStringElement)的过程中加载的UIImage怎么样?
  • 有没有地方我应该调用GC.Collect()来更好地安排集合的空间,以便在发生GC时不会对响应性造成任何影响?
  • 分代垃圾收集器是否可以解决交互问题,是否足够稳定以在生产应用程序中使用? (我相信它在MonoDevelop 3.0.2/MT 4.3.3中仍被称为“实验性”)
  • 我需要在DidReceiveMemoryWarning中做什么以减少iOS射击我的应用程序的可能性?由于每个不可见的 View Controller 似乎都得到了此调用,因此我假设我应该清理该 View Controller 的资源...我应该在ViewDidUnload中做同样的事情吗?
  • 我似乎没有调用我的ViewDidUnload(即使在得到DidReceiveMemoryWarning之后)。实际上,我不记得曾经在日志中看到它。如果iOS在DidReceiveMemoryWarning之后始终调用我的ViewDidUnload,我可以在ViewDidUnload中进行所有清理...在ViewDidUnload和DidReceiveMemoryWarning之间划分清理责任的最佳方法是什么?

  • 我为这个问题的一般性表示歉意-对于白皮书来说,这似乎是一个好话题,但我找不到任何...

    更新:使问题更具体:在使用Instruments和Xamarin Heapshot Profiler之后,对我来说很明显,当用户弹出导航堆栈时,我正在泄漏UIViewControllers。 Rolf为此提交了一个bug,它有两次欺骗,所以这不仅仅是我一个人,这是一个真正的问题。不幸的是,对于泄漏的UIViewControllers,我还没有找到好的解决方法-我没有找到在其上调用Dispose()的好地方。释放由ViewDidLoad分配的资源的自然位置是在ViewDidUnload消息中,但是它从未在模拟器上被调用过,因此我的内存占用量一直在增长。在设备上,我确实看到了DidReceiveMemoryWarning,但是我不愿意将它用作释放我的viewcontroller及其资源的地方,因为我不能保证iOS会真正卸载我的 View ,因此不能保证将再次调用我的ViewDidLoad。两者之一(导致ViewDidAppear可能需要针对其基础资源被处置的情况进行防御性编码)。我很乐意就如何摆脱困境提供一些建议...

    最佳答案

    我花了几天的时间在MT.D源代码和探查器中。虽然我仍在寻找有关实现DidReceiveMemoryWarning和ViewDidUnload的最佳设计模式的一般指导,但我确实有一些共同的见解可供分享,这可能对某人有用:

  • MonoTouch.Dialog的行为非常好。在正常使用情况下,它不会泄漏任何资源。它在DVC.Root下保留一个控件树,并且每个Element的Dispose方法正确地处理基础UIKit控件。如果您已经替换了DVC.Root,则甚至不必担心处理旧的RootElement-属性 setter 会自动为您处理它。总体而言,MT.D似乎没有遭受任何重大的内存问题。有一个异常(exception)-参见下文。
  • 在创建自己的自定义元素(例如MultilineEntryElement)时,请确保覆盖Dispose(bool)方法,处置基础UIKit控件(例如UITextView),并链接基类Dispose()方法。 Miguel的MT.D github项目中的源代码提供了许多很好的示例。所有元素都实现标准的Dispose模式(尽管它们省略了调用Dispose(false)的析构函数/定型器)。
  • 在实现自定义 View Controller 时,通常不需要在UIViewController子类,TableView DataSource或Delegate类上实现Dispose。当 View Controller 获取GC时,它将在其引用上正确调用Dispose。您在数据源中分配的所有单元将被正确处理。
  • 作为(3)的一个异常(exception)-将自己的 subview 添加到TableView的单元格时遇到了一个令人讨厌的问题。此 subview 是我创建的名为“UICheckbox”的控件,该控件最终继承自UIImageView,该控件具有两个UIImage(打开和关闭)和一个名为Clicked的公共(public)事件。我只会在引用数据源成员的事件处理程序挂接到该事件时遇到一个问题(如果该事件处理程序未引用数据源或 Controller 本身,那么一切都很好)。但是,当满足上述条件且 Controller 被解雇时,显然存在某些循环,GC无法确定,并且我放在TableView上的每个UICheckbox都泄漏了(连同其图像)。我发现解决此问题的唯一方法是向ViewDidDisappear中添加代码以处置ViewController并清理其状态IFF,该状态已不再位于导航堆栈中的任何位置。这很hacky,但是可以用。
  • 通常,我遵循以下模板在 View Controller 中分配对象:
  • 在构造函数中不分配任何内容(仅用于传递状态)
  • 在ViewDidLoad中创建一个控制树(并将其放置在ViewDidUnload中)。在XAML中考虑“InitializeComponent”(如果有帮助的话)。如果UIViewController将DialogViewController插入导航堆栈,则ViewDidLoad是创建DVC的好地方。
  • 在ViewDidAppear的控件树中初始化值。例如。您可以使用此方法添加/删除/替换DVC的元素,节,甚至根。但是不要创建新的DVC。
  • 当用户向上浏览导航堆栈时,存在泄漏ViewController的普遍问题(我在问题的“更新”中引用了bugzilla链接)。这也会影响MT.D。有一个相当简单的解决方法-在父 View Controller 的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的协议(protocol):前者始终交付给每个 View Controller ,而后者仅发送给当前未显示的 View Controller ,并且不深于导航堆栈的根。最后,我决定忽略DidReceiveMemoryWarning,因为我确实没有缓存和可以转储的图像(根据iOS指南)。在ViewDidUnload中,我释放在ViewDidLoad中分配的所有资源。
  • 我的应用程序有一个TabBar,其中每个选项卡都承载一个UINavigationController,其中大多数都推送一个DialogViewController。我正在处理的一个问题是,在ViewDidUnload放开对它的引用后,会泄漏DialogViewController。我曾尝试在ViewDidUnload中部署DVC,但iOS一直想重新调用它,并且在GC对象对象上调用选择器时遇到异常。我发现了原因-导航 Controller 在其ViewControllers数组中保留了DVC。解决方案是通过在其位置创建一个零长度数组来释放该数组-在ViewDidUnload中:
    this.ViewControllers = new UIViewController[0];
    

  • 现在,旧阵列将进行GC处理,DVC也将进行处理,因为不再有指向它的数据了。 iOS永远不会重新调用该对象。注意-无需在DVC上调用Dispose。

    关于ios - 使用MonoTouch和MonoTouch.Dialog进行内存/资源管理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10989473/

    相关文章:

    C# PresentViewController 到 Storyboard 中的 View Controller

    iOS Collection View : Loading Data Asynchronously

    ios - 如何删除应用程序沙箱中的文件?

    c++ - 在 Swift 代码中调用 C++ 函数时读取文件失败

    asp.net - 使用 umbraco 的 iis 应用程序池过度使用内存

    javascript - Nodejs/expressjs 中请求参数的字符串不可变性问题

    ios - iOS 上的蓝牙和播放列表

    ios - 如何验证应用程序内购买的 iTunes Store 收据

    c - 这是缓冲区溢出的示例吗?

    c# - MonoGame - 在 iOS 上使用 SpriteBatch 平铺 Texture2D