我遇到了MonoTouch的问题,即使UIViewControllers从导航堆栈中弹出,它们仍永远保留在内存中。
我有一个UINavigationController,它包含一个带按钮的UIViewController。单击该按钮将名为ThreadingViewController的自定义UIViewController推送到导航堆栈。
ThreadingViewController使用NSTimer.CreateRepeatingScheduledTimer和Thread来每隔一秒更新标签文本。
当用户点击“返回”以弹回根视图时,Mono Profiler会说我的ThreadingViewController仍然存在于内存中。 Profiler告诉我它与NSAction和/或ThreadStart有关,它引用了ThreadingViewController,使它保持活着状态。我可以通过检查探查器中的“反向引用”复选框来看到这一点。
这意味着如果用户在根ViewController和自定义ThreadingViewController之间向后和向前单击100次,则内存中将有100个此ViewController实例。它不是垃圾收集。
在ViewDidDisappear中,我尝试中止线程,将其设置为null,但无济于事。
我需要做些什么才能让MonoTouch正确清理/调整此ThreadingViewController?
以下是重现问题的完整(仅限C#)源代码:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using System.Threading;
namespace MemoryTests
{
[Register("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
private UIWindow window;
private UINavigationController rootNavigationController;
private RootScreen rootScreen;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
window = new UIWindow(UIScreen.MainScreen.Bounds);
rootScreen = new RootScreen();
rootNavigationController = new UINavigationController(rootScreen);
window.RootViewController = rootNavigationController;
window.MakeKeyAndVisible();
return true;
}
}
public class RootScreen : UIViewController
{
private UIButton button;
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.Title = "Root Screen";
// Add a button
button = new UIButton(UIButtonType.RoundedRect);
button.SetTitle("Click me", UIControlState.Normal);
button.Frame = new RectangleF(100, 100, 120, 44);
this.View.Add(button);
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
button.TouchUpInside += PushThreadingViewController;
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
button.TouchUpInside -= PushThreadingViewController;
}
private void PushThreadingViewController(object sender, EventArgs e)
{
var threadingViewController = new ThreadingViewController();
NavigationController.PushViewController(threadingViewController, true);
}
}
public class ThreadingViewController : UIViewController
{
private UILabel label;
private NSTimer timer;
private int counter;
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.Title = "Threading Screen";
// Add a label
label = new UILabel();
label.Frame = new RectangleF(0f, 200f, 320f, 44f);
label.Text = "Count: 0";
this.View.Add(label);
// Start a timer
var timerThread = new Thread(StartTimer as ThreadStart);
timerThread.Start();
}
public override void ViewDidDisappear (bool animated)
{
base.ViewDidDisappear(animated);
timer.Dispose();
timer = null;
// Do I need to clean up more Threading things here?
}
[Export("StartTimer")]
private void StartTimer()
{
using (var pool = new NSAutoreleasePool())
{
timer = NSTimer.CreateRepeatingScheduledTimer(1d, TimerTicked);
NSRunLoop.Current.Run();
}
}
private void TimerTicked()
{
InvokeOnMainThread(() => {
label.Text = "Count: " + counter;
counter++;
});
}
}
}
以下是分析器的屏幕截图,告诉我在内存中有3个ThreadingViewController实例:
干杯。
答案 0 :(得分:1)
我认为问题在于:
timer.Dispose();
尝试将其更改为:
timer.Invalidate();
NSTimer
是唯一使用NSAction
的内容。
另外,我认为这不是很有用:
var timerThread = new Thread(StartTimer as ThreadStart);
timerThread.Start();
原因:
PS:我还没有测试过上面的代码。但我完全相信你必须Invalidate()
NSTimers
以便当你不再需要它们时它们会停止运行。
答案 1 :(得分:0)
随时连接点击处理程序,例如:
button.TouchUpInside + = PushThreadingViewController;
您还必须断开连接。我建议将上面的代码行移到ViewDidAppear覆盖中,并将下面的代码行添加到ViewDidDisappear覆盖中:
button.TouchUpInside - = PushThreadingViewController;
我还建议您在将其推入导航堆栈之前检查threadingViewController是否为null并且仅创建新实例(如果是)。这样,如果它还没有被垃圾收集,它每次都会使用相同的一个。
答案 2 :(得分:0)
小心ViewDidDisappear。您可以在任何时候调用View-Controller控制另一个View-Controller(调用PresentViewController,调用共享UI,显示iAd等)。如果您只想在从导航堆栈中删除View-Controller时做出反应,我建议您:
public override void DidMoveToParentViewController(UIViewController parent)
{
base.DidMoveToParentViewController(parent);
if(parent == null)
Cleanup();
}
尝试从清理()方法中调用 ReleaseOutlets ()(从Xamarin Studio XIB处理器生成),以及取消触摸事件处理程序改善情况。