这是MonoTouch GC中的错误吗?

Dan*_*mov 33 garbage-collection xamarin.ios xamarin

注意:我创建了一个简单的项目 - 您可以看到故事板之间UIButtonCustomButton故事板中的切换类型如何改变GC行为.

我正试图让我的脑袋缠绕着MonoTouch垃圾收集器.
该问题类似于MT 4.0中修复的问题,但是使用了继承类型.

为了说明它,请考虑两个视图控制器,父级和子级.

子视图包含一个UIButton可以随时写入控制台的视图.
Controller的Dispose方法会引发异常,因此很难错过.

这里是子视图控制器:

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();

    sayHiButton.TouchUpInside += (sender, e) =>
        SayHi();
    }
}

void SayHi()
{
    Console.WriteLine("Hi");
}

protected override void Dispose (bool disposing)
{
    throw new Exception("Hey! I've just been collected.");
    base.Dispose (disposing);
}
Run Code Online (Sandbox Code Playgroud)

父视图控制器只显示子控制器并设置一个计时器来关闭它并运行GC:

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();

    var child = (ChildViewController)Storyboard.InstantiateViewController("ChildViewController");

    NSTimer.CreateScheduledTimer(2, () => {
        DismissViewController(false, null);
        GC.Collect();
    });

    PresentViewController(child, false, null);
}
Run Code Online (Sandbox Code Playgroud)

如果你运行这个代码,它可以预测会ChildViewController.Dispose()从其终结器中调用内部崩溃,因为子控制器已被垃圾收集.凉.

现在打开故事板并将按钮类型更改为CustomButton.MonoDevelop将生成一个简单的UIButton子类:

[Register ("CustomButton")]
public partial class CustomButton : UIButton
{
    public CoolButton (IntPtr handle) : base (handle)
    {
    }

    void ReleaseDesignerOutlets()
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

以某种方式改变按钮类型CustomButton足以欺骗垃圾收集器使思维儿童控制器还没有资格收集.

怎么会这样?

Rol*_*nge 46

这是一个不幸的副作用MonoTouch(谁是垃圾收集)必须住在一个参考计数世界(ObjectiveC).

需要一些信息才能了解正在发生的事情:

  • 对于每个托管对象(从NSObject派生),都有一个相应的本机对象.
  • 对于自定义托管类(从UIButton或UIView等框架类派生),托管对象必须保持活动状态,直到释放本机对象为止[1].它的工作方式是当本机对象的引用计数为1时,我们不会阻止托管实例进行垃圾回收.一旦引用计数增加到1以上,我们就会阻止托管实例收集垃圾.

在您的情况下发生的是一个循环,它跨越MonoTouch/ObjectiveC桥,由于上述规则,GC无法确定是否可以收集循环.

这是发生的事情:

  • 您的ChildViewController有一个sayHiButton.本机ChildViewController将保留此按钮,因此其引用计数将为2(托管的CustomButton实例持有一个引用+本机ChildViewController持有的一个引用).
  • TouchUpInside事件处理程序具有对ChildViewController实例的引用.

现在您看到CustomButton实例将不会被释放,因为它的引用计数为2.并且不会释放ChildViewController实例,因为CustomButton的事件处理程序具有对它的引用.

有几种方法可以打破周期来解决这个问题:

  • 不再需要时分离事件处理程序.
  • 当您不再需要时,请处理ChildViewController.

[1]这是因为托管对象可能包含用户状态.对于镜像相应本机对象(例如托管UIView实例)的托管对象,MonoTouch知道实例不能包含任何状态,因此只要没有托管代码具有对托管实例的引用,GC就可以收集它.如果稍后需要托管实例,我们只需创建一个新实例.

  • @miguel:请做!我喜欢使用MonoTouch,我知道你们都非常努力地提供最好的移动开发体验,但是碰到漏洞抽象**并且不知道如何解决它们是令人沮丧的**.Xamarin网站诱使你认为一切都将"正常工作",包括LINQ,泛型,GC和其他东西.它在很大程度上起作用,但是当它没有时,没有太多的文档,好像你不希望人们知道GC循环,蹦床问题和各种权衡.你的产品并不完美,对我来说很好* - 只是不要把它藏起来. (19认同)
  • @Dan Abramov,我们正在积极调查一个系统,以避免首先出现这些循环.同时,我们将考虑将其合并到我们的文档中. (4认同)
  • 这是一个很好的解释!我希望Xamarin上有一些页面解释这些问题和解决方案模式. (3认同)
  • 我发现的最佳实践是在`ViewWillAppear`中设置事件处理内容并在`ViewDidDisappear`中将其拆除.它很便宜,不需要你手动`Dispose`视图控制器.还要小心通知订阅,我倾向于设置和拆除与事件相同的方式.最后进行运行时检查以不多次订阅事件,因为`ViewWillAppear`可能被多次调用. (2认同)