问题描述
我试图让我的脑袋缠住MonoTouch垃圾回收器。
问题类似于,但是有继承类型。
为了说明这一点,考虑两个视图控制器,父母和孩子。
子视图包含一个单击的 UIButton
,可以轻松写入控制台。
控制器的 Dispose
方法抛出异常,所以很难错过。
这里是子视图控制器:
public override void ViewDidLoad()
{
base.ViewDidLoad();
sayHiButton.TouchUpInside + =(sender,e)=>
SayHi();
void SayHi()
{
Console.WriteLine(Hi);
}
保护覆盖无效Dispose(布尔处置)
{
抛出新的异常(嘿!我刚刚被收集。);
base.Dispose(处置);
$ b $ p
$ b 父视图控制器只显示子控制器并设置一个计时器来解除它并运行GC:
public override void ViewDidLoad()
{
ase.ViewDidLoad();
var child =(ChildViewController)Storyboard.InstantiateViewController(ChildViewController);
NSTimer.CreateScheduledTimer(2,()=> {
DismissViewController(false,null);
GC.Collect();
});
PresentViewController(child,false,null);
}
如果你运行这段代码,它可能会崩溃在 ChildViewController.Dispose()
从其终结器中调用,因为子控制器已被垃圾收集。酷。
现在打开故事板并将按钮类型更改为 CustomButton
。 MonoDevelop将生成一个简单的 UIButton
子类:
$ b
[Register(CustomButton )]
public partial class CustomButton:UIButton
{
public CoolButton(IntPtr handle):base(handle)
{
}
void ReleaseDesignerOutlets()
{
}
}
将按钮类型更改为 CustomButton
足以让垃圾收集器陷入认为小孩控制器尚未被收集的资格。
这是怎么回事?
解决方案这是MonoTouch(谁是垃圾回收)不得不生活的一个不幸的副作用一个引用计数的世界(ObjectiveC)。
需要了解一些信息:
- 对于每个托管对象(派生自NSObject),都有一个相应的本地对象。
- 对于c ustom托管类(从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就可以收集它。如果稍后需要托管实例,则只需创建一个新实例。
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.
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.
Here goes child view controller:
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);
}
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);
}
If you run this code, it predictably crashes inside ChildViewController.Dispose()
called from its finalizer because child controller has been garbage collected. Cool.
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()
{
}
}
Somehow changing the button type to CustomButton
is enough to trick garbage collector into thinking child controller is not yet eligible for collection.
How is that so?
解决方案 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:
- For every managed object (derived from NSObject), there is a corresponding native object.
- For custom managed classes (derived from framework classes such as UIButton or UIView), the managed object must stay alive until the native object is freed [1]. The way it works is that when a native object has a reference count of 1, we do not prevent the managed instance from getting garbage collected. As soon as the reference count increases above 1, we prevent the managed instance from getting garbage collected.
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.
This is what happens:
- Your ChildViewController has a sayHiButton. The native ChildViewController will retain this button, so its reference count will be 2 (one reference held by the managed CustomButton instance + one reference held by the native ChildViewController).
- The TouchUpInside event handler has a reference to the ChildViewController instance.
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:
- Detach the event handler when you no longer need it.
- Dispose the ChildViewController when you no longer need it.
[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中的错误吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!