iOS和Xamarin有稍微有点棘手的关系。iOS使用引用计数来管理和处理内存。对象的引用计数在添加和删除引用时会被增加和减少。当引用计数变为0时,对象将被删除并释放内存。Objective C和Swift中的自动引用计数可以帮助解决这个问题,但在使用本地iOS语言开发时,仍然很难做到百分之百正确,可能会出现悬空指针和内存泄漏。
在Xamarin中为iOS编码时,我们必须考虑到引用计数,因为我们将使用iOS本地内存对象。为了与iOS操作系统通信,Xamarin创建了所谓的Peers来为我们管理引用计数。有两种类型的Peers-框架Peers和用户Peers。框架Peers是围绕着众所周知的iOS对象的托管包装器。框架Peers是无状态的,因此不会对底层iOS对象保持强引用,并且可以在需要时由垃圾收集器清理-不会导致内存泄漏。
用户Peers是从框架Peers派生的自定义托管对象。用户Peers包含状态,因此即使您的代码没有对它们的引用,也会被Xamarin框架保持活动状态。例如:
public class MyViewController : UIViewController
{
public string Id { get; set; }
}
我们可以创建一个新的MyViewController,将其添加到视图树中,然后将UIViewController转换为MyViewController。可能没有对这个MyViewController的引用,因此Xamarin需要“根”据此对象,以使其在基础的UIViewController存在时保持活动状态,否则将丢失状态信息。
问题在于,如果我们有两个相互引用的用户对等体,则会创建一个无法自动断开的引用循环-而这种情况经常发生!
考虑以下情况:
public class MyViewController : UIViewController
{
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear (animated);
MyButton.TouchUpInside =+ DoSomething;
}
void DoSomething (object sender, EventArgs e) { ... }
}
Xamarin创建两个相互引用的用户对等体 – 一个用于MyViewController,另一个用于MyButton(因为我们有一个事件处理程序)。因此,这将创建一个引用循环,垃圾收集器无法清除。为了清除它,我们必须取消订阅事件处理程序,通常在ViewDidDisappear处理程序中完成 -例如:
public override void ViewDidDisappear(bool animated)
{
ProcessButton.TouchUpInside -= DoSomething;
base.ViewDidDisappear (animated);
}
总是取消订阅iOS事件处理程序。
如何诊断这些内存泄漏
诊断这些内存问题的好方法是在从iOS包装类派生的类的finalizer中添加一些代码,例如UIViewControllers
。(尽管只将其放在调试版本中而不是发布版本中,因为它会导致速度变慢)。
public partial class MyViewController : UIViewController
{
#if DEBUG
static int _counter;
#endif
protected MyViewController (IntPtr handle) : base (handle)
{
#if DEBUG
Interlocked.Increment (ref _counter);
Debug.WriteLine ("MyViewController Instances {0}.", _counter);
#endif
}
#if DEBUG
~MyViewController()
{
Debug.WriteLine ("ViewController deleted, {0} instances left.",
Interlocked.Decrement(ref _counter));
}
#endif
}
因此,Xamarin在iOS上的内存管理并没有问题,但是您必须注意这些针对iOS运行的特定“小技巧”。
Thomas Bandt有一篇名为“Xamarin.iOS内存陷阱”的优秀页面,详细介绍了这个问题,并提供了一些非常有用的提示和技巧。