这是 MonoTouch GC 中的错误吗? [英] Is this a bug in MonoTouch GC?

查看:26
本文介绍了这是 MonoTouch GC 中的错误吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Note: I've created a simple project—you can see how switching types between UIButton and CustomButton in storyboard changes GC behavior.

我正在努力解决 MonoTouch 垃圾收集器的问题.
该问题类似于MT 4.0 中修复的问题,但具有继承类型.

I'm trying to get my head wrapped around MonoTouch garbage collector.
The issue is similar to the one fixed in MT 4.0, however with inherited types.

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

To illustrate it, consider two view controllers, parent and child.

孩子的视图包含一个 UIButton,它会在点击时写入控制台.
控制器的 Dispose 方法抛出异常,所以很难错过.

Child's view contains a single UIButton that writes to console on tap.
Controller's Dispose method throws an exception so it's hard to miss.

这是子视图控制器:

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);
}

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

Parent view controller just presents child controller and sets a timer to dismiss it and run 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);
}

如果您运行此代码,它会在从其终结器调用的 ChildViewController.Dispose() 内崩溃,因为子控制器已被垃圾收集.很酷.

If you run this code, it predictably crashes inside ChildViewController.Dispose() called from its finalizer because child controller has been garbage collected. Cool.

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

Now open the storyboard and change button type to CustomButton. MonoDevelop will generate a simple UIButton subclass:

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

    void ReleaseDesignerOutlets()
    {
    }
}

以某种方式将按钮类型更改为 CustomButton 足以让垃圾收集器认为子控制器尚不符合收集条件.

Somehow changing the button type to CustomButton is enough to trick garbage collector into thinking child controller is not yet eligible for collection.

这是怎么回事?

推荐答案

这是 MonoTouch(垃圾收集器)不得不生活在引用计数世界 (ObjectiveC) 中的不幸副作用.

This is an unfortunate side-effect of MonoTouch (who is garbage collected) having to live in a reference counted world (ObjectiveC).

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

There are a few pieces of information required to be able to understand what's going on:

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

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

What happens in your case is a cycle, which crosses the MonoTouch/ObjectiveC bridge and due to the above rules, the GC can't determine that the cycle can be collected.

事情是这样的:

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

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

Now you see that the CustomButton instance will not be freed, because its reference count is 2. And the ChildViewController instance will not be freed because the CustomButton's event handler has a reference to it.

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

There are a couple of ways to break the cycle to fix this:

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

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

[1] This is because a managed object may contain user state. For managed objects which are mirroring a corresponding native object (such as the managed UIView instance) MonoTouch knows that the instance can not contain any state, so as soon as no managed code has a reference to the managed instance, the GC can collect it. If a managed instance is required at a later stage, we just create a new one.

这篇关于这是 MonoTouch GC 中的错误吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆