2

Consider this simple example:

public partial class TableViewController : UITableViewController
{
    public TableViewController (IntPtr handle) : base (handle)
    {
    }

    protected override void Dispose (bool disposing)
    {
        Console.WriteLine (String.Format ("{0} controller disposed - {1}", this.GetType (), this.GetHashCode ()));

        base.Dispose (disposing);
    }

    public override void ViewDidLoad ()
    {
        //TableView.Source = new TableSource(this);
        TableView.Source = new TableSource();
    }
}

public class TableSource : UITableViewSource {

    private TableViewController controller;
    string CellIdentifier = "TableCell";

    public TableSource ()
    {

    }

    public TableSource (TableViewController controller)
    {
        this.controller = controller;
    }

    public override nint RowsInSection (UITableView tableview, nint section)
    {
        return 1;
    }

    public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
    {
        UITableViewCell cell = tableView.DequeueReusableCell (CellIdentifier);

        //if there are no cells to reuse, create a new one
        if (cell == null){
            cell = new UITableViewCell (UITableViewCellStyle.Default, CellIdentifier);
        }

        cell.TextLabel.Text = "test";

        return cell;
    }
}

I've noticed that the view controller (TableViewController) is never released. The table view controller has a reference to the data source, but the data source also has a reference to the table view controller.

With TableView.Source = new TableSource(); the view controller gets released, with TableView.Source = new TableSource(this); it's not.

How should this reference cycle be broken so that everything get released?

Edit:

Now I tried the WeakReference:

Through using a WeakReference the Dispose method is called, when the view controller is popped off the navigation stack.

In ViewDidLoad:

TableView.Source = new TableSource(new WeakReference<TableViewController> (this));

In the datasource:

private WeakReference<TableViewController> controller;

public TableSource (WeakReference<TableViewController> controller)
{
    this.controller = controller;
}

I built this into my real project, but how can I access my controller? I get the message

Type 'System.WeakReference' does not contain a definition for 'xxx' and no extension method 'xxx' of type 'System.WeakReference' could be found. Are you missing an assembly reference?

testing
  • 19,681
  • 50
  • 236
  • 417
  • What do you mean by 'released'? The .net GC can cope with circular references so they will get cleaned up, have a look here http://stackoverflow.com/questions/8840567/garbage-collector-and-circular-reference – Mant101 Aug 21 '15 at 13:05
  • 1
    Have you tried to use a weak var? – Carlos Ricardo Aug 21 '15 at 13:11
  • @Mant101: The `Dispose` function is not called, when the view controller is popped off the navigation stack. – testing Aug 21 '15 at 13:20
  • 1
    Use `WeakReference` whenever you have parent-child-relations with Xamarin.iOS to prevent strong reference cycles. In your particular case, you can override the required methods directly since you are deriving from `UITableViewController`. No need to create a separate data source or delegate or to to derive from some other interface. – Krumelur Aug 26 '15 at 14:35
  • @Krumelur: The reason why I use a separate data source is to have a better separation, when viewing the code. Especially when having more complex projects (the above code is only for demonstration purposes). So implementing `RowsInSection`, `GetCell` and so in the `UITableViewController` is enough to get the view controller released? I tried that and it seems to work (no need for setting `WeakDataSource`). But if you use a separa data source class and you want to access the *controller* `WeakReference` is your only chance? – testing Aug 26 '15 at 15:37
  • Well, you have to break the cycle. The problem your code has, is that both your source and the controller are subclasses of native objects. So to keep the managed objects around, a GC handle gets created (native to managed). Next, you pass the controller into the source: another reference to the controller gets created. And: your controller (or better its view) is part of the native view hierarchy. To break the cycle you have to remove the controller from the UI and `Dispose()` it. Or, in the source, set the controller to NULL and in the controller, set the source to NULL. – Krumelur Aug 26 '15 at 18:33
  • See also: https://developer.xamarin.com/guides/cross-platform/deployment,_testing,_and_metrics/memory_perf_best_practices/#iOS_Specific_Memory_Considerations and http://krumelur.me/2015/04/27/xamarin-ios-the-garbage-collector-and-me/ – Krumelur Aug 26 '15 at 18:34

3 Answers3

4

You work with Xamarin, as I see? Have you tried WeakReference? https://msdn.microsoft.com/en-us/library/system.weakreference(v=vs.110).aspx

PS:

private WeakReference weakController; 

to set:

this.weakController = new WeakReference(controller); 

to get:

if (weakController.isAlive)
{
  TableViewController controller = weakController.Target as TableViewController;
}
Igor
  • 1,537
  • 10
  • 12
  • Can you have a look at my edited question? How do I access my *controller* when it is of type `WeakReference`? – testing Aug 21 '15 at 13:54
  • As far as I now, you need something like this: private WeakReference weakController; to set: this.weakController = new WeakReference(controller); to get: TableViewController controller = weakController.Target as TableViewController; – Igor Aug 21 '15 at 14:07
  • Can I create the target controller in the constructor of my datasource or do I have to do this everytime I want to access the *weakController*? – testing Aug 21 '15 at 14:10
  • every time, in constructor you will create a strong reference – Igor Aug 21 '15 at 14:12
  • Do I have to worry about [short and long weak references](http://www.philosophicalgeek.com/2014/08/20/short-vs-long-weak-references-and-object-resurrection/)? – testing Aug 21 '15 at 14:14
  • As for me, you shouldn't, but I can be mistaken. – Igor Aug 21 '15 at 14:19
  • Isn't there an overhead if I everytime have to instantiate the *controller*, when I want to access it? Is the [`TryGetTarget` method](http://www.philosophicalgeek.com/2014/08/14/prefer-weakreferencet-to-weakreference/) the same as your code, but only for .NET 4.5? – testing Aug 21 '15 at 14:22
  • I had some issues with WeakReference in my last project, that's why I used WeakReference. I didn't remember all details now, but you can try it yourself. – Igor Aug 21 '15 at 14:34
-1

change

public partial class TableViewController : UITableViewController

to

public partial class TableViewController : UITableViewController, UITableViewSource

and in ViewDidLoad just do

self.TableView.Source = self;

the source property is a weak reference internally already so your have no problem of managing that. It is a convenience property to make the tbaleviewcontroller the delegate and datasource altogether. (Just like you would in native iOS)

der_michael
  • 3,151
  • 1
  • 24
  • 43
  • 1
    You are inheriting from two different base classes and that of course isn't allowed. You don't need to inherit from 'UITableViewSource' if the intent is to use a weak datasource. – SKall Aug 23 '15 at 19:08
-1

You can move the methods into the controller itself which would be less of a hassle than WeakReference. Then mark them with export attribute which then allows you to set the UITableView.WeakDataSource property to the controller itself.

[Export("tableView:numberOfRowsInSection:")]
public nint RowsInSection (UITableView tableview, nint section)

[Export("tableView:cellForRowAtIndexPath:")]
public UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)

Once they are moved you can attach the datasource:

public override void ViewDidLoad ()
{
    TableView.WeakDataSource = this;
}
SKall
  • 5,234
  • 1
  • 16
  • 25
  • Don't know who downvoted you ... I tried it your way, but it seems that the view controller is not getting disposed. Do I something wrong? – testing Aug 24 '15 at 13:19