AnderCover
AnderCover

Reputation: 2661

Monotouch UITableviewCells never destroyed

I have a controller which has a UITableView named WaitTableView. It has only one cell, here is the code of the UITableViewCell class :

    public class TableViewWaitCell : UITableViewCell
    {
        public UIActivityIndicatorView activityIndicator = new UIActivityIndicatorView(UIActivityIndicatorViewStyle.Gray);
        public UILabel lblLoading = new UILabel();

        public TableViewWaitCell(UITableViewCellStyle style, string reuseIdentifier) : base (style, reuseIdentifier)
        {
            this.SelectionStyle = UITableViewCellSelectionStyle.None;
        }

        ~TableViewWaitCell(){
            System.Console.WriteLine("TableViewWaitCell.~TableViewWaitCell");
            lblLoading = null;
            activityIndicator = null;   
            System.GC.Collect();
        }
       protected override void Dispose (bool disposing){
            System.Console.WriteLine("TableViewWaitCell.Dispose");
            lblLoading = null;
            activityIndicator = null;
            base.Dispose (disposing);
            GC.Collect();
        }

        public override void Draw (System.Drawing.RectangleF rect)
        {
            base.Draw (rect);

            var context = UIGraphics.GetCurrentContext();
            var gradient = new CGGradient(
            CGColorSpace.CreateDeviceRGB(),
                new float[] { 1f, 1f, 1f, 1f,
                              0.68f, 0.68f, 0.72f, 1f },
                new float[] { 0f, 1f } );
                context.DrawLinearGradient(gradient,
                    new PointF(rect.X+rect.Width/2, rect.Y),
                    new PointF(rect.X+rect.Width/2, rect.Y+rect.Height),
                    CGGradientDrawingOptions.DrawsAfterEndLocation);

            var activityIndicatorViewFrame = new RectangleF(rect.X + rect.Width/2-10, rect.Y+10, 20, 20);
            this.activityIndicator  .Frame = activityIndicatorViewFrame;
            this.activityIndicator.AutoresizingMask = UIViewAutoresizing.FlexibleDimensions;
            this.activityIndicator.StartAnimating();
            this.AddSubview(this.activityIndicator);

            var labelFrame = new RectangleF(rect.X, rect.Y+10+activityIndicatorViewFrame.Height, rect.Width, 35);
            this.lblLoading.Frame = labelFrame;
            this.lblLoading.AutoresizingMask = UIViewAutoresizing.FlexibleDimensions;
            this.lblLoading.TextColor = UIColor.Black;
            this.lblLoading.BackgroundColor = UIColor.Clear;
            this.lblLoading.TextAlignment = UITextAlignment.Center;
            this.lblLoading.Text = Dictionary.GetValue("Loading");
            this.AddSubview(this.lblLoading);
        }
    }

here is the ViewWillDisappear method of the main UIViewController :

        public override void ViewWillDisappear (bool animated)
    {
        Console.WriteLine("SpotlightView.ViewWillDisappear");
        if(this.PopoverController!=null)
            this.PopoverController.Dismiss(true);
        this.PopoverController = null;
        this.tableView.RemoveFromSuperview();
        this.WaitTableView.RemoveFromSuperview();
        this.searchBar.RemoveFromSuperview();
        this.tableView.Source = null;
        this.tableView.Dispose();
        this.tableView = null;
        this.WaitTableView.Source = null;
        this.WaitTableView.Dispose();
        this.WaitTableView = null;
        this.searchBar.Delegate = null;
        this.searchBar.Dispose();
        this.searchBar = null;

        base.ViewWillDisappear (animated);
    }

My problem is that neither the destructor nor the Dispose of my cells got called. When I run heapshot the number of instances of the TableViewWaitCell class grow up has I navigate through my app. I don't understand how the cells life-cycle is managed in Monotouch, what could have I done wrong ?

Upvotes: 0

Views: 845

Answers (2)

AnderCover
AnderCover

Reputation: 2661

The problem came from an EventHandler which referenced a method of my view controller thus prenventing the collection of my cells and my controller.

Upvotes: 0

holmes
holmes

Reputation: 1341

I do not see anything that would cause this problem in the code you shared. However you do not show how the cell is constructed and stored. Your objects may be kept alive by a root you are not showing in your sample code. I have created a sample below that shows Dispose and the finalizer being called.

Using the simulator the table view and the cell are collected quickly. Usually after a press the of the 'Show Table' button the second time.

Running on the device shows a different behavior. The table and cell are not collected right away. The simplest explanation is that the GC is 'tuned' to only run when it needs to. So if your app is not using much memory, all the objects will continue to live.

There are two things you can do to force the GC to run as shown in the sample.

showTable.TouchUpInside += delegate {
    navController.PushViewController (new MyViewController (), true);
    // Not a great idea to call Collect but you could to force it.
    // GC.Collect ();
};

allocate.TouchUpInside += delegate {
    // Trigger the GC by creating a bunch of objects
    System.Collections.Generic.List<object> list = new System.Collections.Generic.List <object> ();
    for (int i=0; i<2048; i++)
    {
        list.Add (new object ());
    }
};

First you can call GC.Collect. However I do not recommend that. The GC will run best when you let it run when it wants to. (In most cases.) When is it acceptable to call GC.Collect?

Second just keep writing code and let the GC decide what is best. In the sample I added another button that allocates a bunch of objects and adds them to a list. So if you toggle between the table view and the main view a few times then press the allocate button a couple of times you should see the finalizers run.

using System;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using MonoTouch.CoreGraphics;
using System.Drawing;

namespace delete20130320
{
    [Register ("AppDelegate")]
    public partial class AppDelegate : UIApplicationDelegate
    {
        UIWindow window;


        public override bool FinishedLaunching (UIApplication app, NSDictionary options)
        {
            window = new UIWindow (UIScreen.MainScreen.Bounds);

            var mainView = new UIViewController ();
            var showTable = UIButton.FromType (UIButtonType.RoundedRect);
            showTable.Frame = new System.Drawing.RectangleF (10, 10, 150, 35);
            showTable.SetTitle ("Show Table", UIControlState.Normal);

            var allocate = UIButton.FromType (UIButtonType.RoundedRect);
            allocate.Frame = new System.Drawing.RectangleF (10, 55, 150, 35);
            allocate.SetTitle ("Allocate", UIControlState.Normal);

            mainView.View.BackgroundColor = UIColor.White;
            mainView.View.Add (showTable);
            mainView.View.Add (allocate);

            var navController = new UINavigationController (mainView);

            showTable.TouchUpInside += delegate {
                navController.PushViewController (new MyViewController (), true);
                // Not a great idea to call Collect but you could to force it.
                // GC.Collect ();
            };

            allocate.TouchUpInside += delegate {
                // Trigger the GC by creating a bunch of objects
                System.Collections.Generic.List<object> list = new System.Collections.Generic.List <object> ();
                for (int i=0; i<2048; i++)
                {
                    list.Add (new object ());
                }
            };


            window.RootViewController = navController;

            window.MakeKeyAndVisible ();

            return true;
        }
    }

    public class MyViewController : UIViewController
    {
        UITableView _tableView;
        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();
            _tableView = new UITableView (this.View.Bounds);
            View.Add (_tableView);

            _tableView.DataSource = new MyDataSource ();
        }

        ~MyViewController ()
        {
            // Bad practice to call other managed objects in finalizer
            // But for sample purposes it will be ok
            Console.WriteLine ("~MyViewController");
        }

        protected override void Dispose (bool disposing)
        {
            // Bad practice to call other managed objects in Dispose
            // But for sample purposes it will be ok
            Console.WriteLine ("MyViewController.Dispose");
            base.Dispose (disposing);
        }

        class MyDataSource : UITableViewDataSource
        {
            public override int RowsInSection (UITableView tableView, int section)
            {
                return 1;
            }

            public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
            {
                var cell = tableView.DequeueReusableCell ("SomeUniqueString");
                if (cell != null)
                    return cell;

                return new TableViewWaitCell (UITableViewCellStyle.Default, "SomeUniqueString");
            }
        }
    }

    public class TableViewWaitCell : UITableViewCell
    {
        public TableViewWaitCell(UITableViewCellStyle style, string reuseIdentifier) : base (style, reuseIdentifier)
        {
            this.SelectionStyle = UITableViewCellSelectionStyle.None;
            this.TextLabel.Text = "Something";
        }

        ~TableViewWaitCell()
        {
            // Bad practice to call other managed objects in finalizer
            // But for sample purposes it will be ok
            System.Console.WriteLine("TableViewWaitCell.~TableViewWaitCell"); 
            // Avoid forcing the GC
            //System.GC.Collect();
        }
        protected override void Dispose (bool disposing)
        {
            // Bad practice to call other managed objects in Dispose
            // But for sample purposes it will be ok
            System.Console.WriteLine("TableViewWaitCell.Dispose");
            base.Dispose (disposing);
            //GC.Collect();
        }
    }
}

Upvotes: 2

Related Questions