views:

51

answers:

2

I’m having a memory issue in my iPhone app. I'm using Monotouch. I have hunted down the problem, by using a static instance counters. The problem has something to do with modal view controllers. When i navigate from a root-viewcontroller to a first-level-viewcontroller and back, i find that the first-level-viewcontroller is garbage-collected. But when i make the first-level-viewcontroller modal by calling PresentModalViewController and i returning by calling DismissModalViewControllerAnimated, i find that the first-level-viewcontroller is not garbage collected. Not even when i call GC.Collect().

Why not? Am i doing something wrong?

What is the best practice for ensuring release of VC’s?

partial class RootViewController : UITableViewController
{
    static int instanceCount;
    static int nextId;
    int instanceId;

    public RootViewController (IntPtr handle) : base(handle)
    {
        instanceCount++;
        instanceId = nextId++;
        Console.WriteLine(string.Format("RootViewController #{0} Count={1}", instanceId, instanceCount));
    }

    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();
        Title = "Root";
        NavigationItem.RightBarButtonItem = new UIBarButtonItem("ModalVC", UIBarButtonItemStyle.Plain, 
            delegate 
            {
                var firstlevelVc = new FirstLevelViewController();
                PresentModalViewController(new UINavigationController(firstlevelVc), true);
            });

        NavigationItem.LeftBarButtonItem = new UIBarButtonItem("PushVC", UIBarButtonItemStyle.Plain, 
            delegate 
            {
                var firstlevelVc = new FirstLevelViewController();
                NavigationController.PushViewController(firstlevelVc, true);                
            });
    }

    public override void ViewDidAppear (bool animated)
    {
        base.ViewDidAppear (animated);
        GC.Collect();
    }

    ~RootViewController()
    {
        instanceCount--;
        Console.WriteLine(string.Format("RootViewController #{0} Count={1}", instanceId, instanceCount));

    }
}
public partial class FirstLevelViewController : UIViewController
{
    static int instanceCount;
    static int nextId;
    int instanceId;
    public FirstLevelViewController (IntPtr handle) : base(handle)
    {
        Initialize ();
    }

    [Export("initWithCoder:")]
    public FirstLevelViewController (NSCoder coder) : base(coder)
    {
        Initialize ();
    }

    public FirstLevelViewController () : base("FirstLevelViewController", null)
    {
        Initialize ();
    }

    void Initialize ()
    {
        instanceCount++;
        instanceId = nextId++;
        Console.WriteLine(string.Format("FirstLevelViewController #{0} Count={1}", instanceId, instanceCount));
    }

    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();
        Title = "1. level";

        NavigationItem.RightBarButtonItem = new UIBarButtonItem("Dismiss modal", 
                                                                UIBarButtonItemStyle.Plain, 
                                                                delegate { ParentViewController.DismissModalViewControllerAnimated(true); });

    }

    ~FirstLevelViewController()
    {
        instanceCount--;
        Console.WriteLine(string.Format("FirstLevelViewController #{0} Count={1}", instanceId, instanceCount));

    }
}
A: 

Finalizer (~method) is being called when reference counter on an object comes to 0, are you sure you do not have a reference somewhere to the UIViewController you use?

To be sure I do not have a reference and ensure GC removal, I implement IDisposable and Dispose method, call it and do the necessary cleaning there (remove all objects from inner collections and such) and I do set the original variable to null. So it looks like:

MyUIViewController cntrl = new MyUIViewController();

... do some stuff

// when done cntrl.Dispose(); cntrl = null;

And that is all.

BTW. I believe you should not call GC.Collect by yourself that frequently, as it stops all threads and does only the cleanup, all work is at that time stopped and is quite heavy on resources.

Hope it helps.

Pavel Sich
If there is a reference to FirstLevelViewController it could be from the UINavigationController created with the statement PresentModalViewController(new UINavigationController(firstlevelVc), true). But as far as i can see the navigationcontroller is unreachable.
Søren Randrup
I only use the finalizer for the purpose of instance count and trace and I only use GC.Collect() to prove my point.
Søren Randrup
And how do you dismiss the modal controller? The template is to call it from the child controller to the top one with this.ParentViewController.DismissModalViewControllerAnimated. Calling it form the child on itself, might cause this. Just a thought.
Pavel Sich
I dismiss by calling ParentViewController.DismissModalViewControllerAnimated(true). (see the code above)
Søren Randrup