Xamarin.iOS, the garbage collector and me

A lot has been written about the Garbage Collector and Xamarin.iOS. Lately, I’ve been hit by some surprises and I want to share my findings. Let me say that there is not a single bug involved! All the issues I describe are either documented or show standard behavior of the .NET runtime. The fact that Xamarin.iOS lives on top of a reference counted world makes things a bit more difficult.

Many thanks to Rolf Bjarne Kvinge for his patience, Marek Safar for sharing the secrets of Mono compiler with me, Rodrigo Moya for helping me with the Xamarin Profiler, James Clancey for his drawings about native reference cycles 🙂 and Chris van Wyk for supporting me in these moments of “good god, this cannot be!”

1. How can Xamarin.iOS possibly know?

Let’s start with something basic. We first create a subclass of UIView and add it to our view controller:

public class CustomView : UIView
{
    public int magicNumber;
}

and this is ViewDidLoad():

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();
    this.Add(new CustomView { magicNumber = 42 });
}

Three things are important to note here:

  1. Our CustomView has a property magicNumber we set to 42.
  2. CustomView is a managed object. It does not exist in the unmanaged world but inherits from the unmanaged UIView base class.
  3. We do not keep a reference around to the CustomView object anywhere.

Let’s ask ourselves: what is the scope of CustomView? When is it eligible for garbage collection? The answer is: as soon as the closing curly brace of ViewDidLoad is hit, it’ll be out of scope – there is no managed reference to it anywhere left, so it could be collected. But it won’t. The call to the Add() method is a native one, no managed reference is kept there either.

In our view controller we also override ViewWillAppear(). In there we use the view controller’s View.Subviews property to inspect the subviews.

public override void ViewWillAppear (bool animated)
{
    base.ViewWillAppear (animated);
    var customView = this.View.Subviews [0] as CustomView;
    Console.WriteLine ("Magic number: " + customView.magicNumber);
}

Easy, clear, logical. But think about it for a second: how the heck does Xamarin.iOS know that the unmanaged subview at index 0 in this array is actually a managed CustomView that can be cast back? The Subviews array is native, our CustomView has long gone out of scope. Not only can we cast it back, we can also access our magicNumber property and it will still have the value 42. Obviously, the managed CustomView has survived. That means something kept a reference to it. This is part of the Xamarin.iOS magic: the runtime keeps a garbage collector handle (GCHandle) that points from the native UIView to the managed CustomView. As long as the native view is alive, our managed counterpart will be kept alive too. If the native view goes away, our managed version can be collected.

Knowing how this works is crucial to understand the upcoming examples.

2. The prematurely collected button

In the introduction we subclassed a UIView. Now, let’s subclass a UIButton. Here’s how you would maybe do it:

public class CustomButton : UIButton
{
    public CustomButton() : base(UIButtonType.System)
    {}

    public int someValue;

    protected override void Dispose (bool disposing)
    {
        Console.WriteLine ("Disposed button");
        base.Dispose (disposing);
    }

    ~CustomButton()
    {
        Console.WriteLine ("Finalized button");
    }
}

We subclass UIButton and since we only want system type buttons, we call the base(UIButtonType.System) constructor. This subclass here also overrides the finalizer and Dispose() to see when the button gets disposed.
Again, in ViewDidLoad(), let’s add an instance of our custom button:

var btn = new CustomButton () {
    Frame = new CoreGraphics.CGRect(0, 20, 320, 40),
    someValue = 4711
};
btn.SetTitle ("Click me", UIControlState.Normal);
btn.TouchUpInside += (sender, e) => {
    btn.RemoveFromSuperview();
};
this.Add (btn);

If the button is clicked, it removes itself from the superview. If you run the code above on the iOS Simulator, you will notice the following in the output pretty immediately:

Finalized button
Disposed button

If you click the button, you’ll get a native exception about a missing selector. What happened here? Looks like our managed button got collected. But if you compare to our first example, where we used a UIView instead of the button, what is different? You might say: “Nothing” and file a bug. But if you google a bit, you’ll maybe find this:

Preferred constructor for UIButtons, but can not be used when subclassing.

The Xamarin.iOS API docs tell us that the constructor we used for our button is a convenience method on the native side. It uses the static UIButton.FromType() and that will create a base class UIButton and return it. This means we never create a CustomButton, but a UIButton instead! The UIButton object however has no idea about our managed CustomButton because there is no GCHandle being retained. Now add this to the variable scope: the btn variable from the code snippet above is out of scope at the end of ViewDidLoad() and will be collected. If we click the button, there is no object to send the click selector to and the app will crash.

The fix is easy: use the paramterless constructor instead.

public CustomButton () : base()
{
}

Afterwards, our button texts will be invisible. That’s because we’re no longer creating a system button but a “default” button with white text. Use something like btn.SetTitleColor(UIColor.Blue, UIControlState.Normal); to set your preferred color.

If we run the example again, the button won’t be collected until it has been removed from its superview. Problem solved and we get the expected behavior.

3. The not-collected image view

We now understand how the UIView subclassing works and we fixed a problem with a UIButton subclass being collected too early. What did we learn? If there is no reference, the object will be collected. Fine. Let’s try that. Here’s a custom UIImageView:

public class CustomImageView : UIImageView
{
    public CustomImageView () : base(UIImage.FromBundle("image.png"))
    {
    }

    protected override void Dispose (bool disposing)
    {
        Console.WriteLine("CustomImageView disposed");
        base.Dispose (disposing);
    }

    ~CustomImageView()
    {
        Console.WriteLine("CustomImageView finalized");
    }
}

Nothing special. It overrides the finalizer and Dispose() to tell us when it gets collected. We create an instance (again, only use a local variable), add it to our controller and also add a plain UIButton that will remove the custom image view if it is clicked:

var imgView = new CustomImageView()
{
    Frame = new CoreGraphics.CGRect(0, 100, 320, 100)
};
this.Add(imgView);

var removeImgBtn = new UIButton(UIButtonType.System)
{
    Frame = new CoreGraphics.CGRect(0, 200, 320, 40)
};
removeImgBtn.SetTitle("Remove image", UIControlState.Normal);
removeImgBtn.TouchUpInside += (sender, e) => {
    imgView.RemoveFromSuperview();
};

In the previous example, the btn got collected the moment we removed it from its superview. Now, we remove a UIImageView from its parent if a button gets clicked. Quiz question: will the image view be collected or not?

The answer might be surprising: no, it won’t!

What’s the difference between CustomButton and CustomImageView? Nothing really, both are UIView subclasses. What we have here is a typical .NET “problem” – I put this in quotes because it is not a real problem, it’s just not understanding GC and strong references. Who has a strong reference to the imgView in the code above? We add the image view to the parent view, so the native view has a reference but this gets removed inside the button’s event handler by removing the image view from its parent. If the image view isn’t collected, there must be another reference”¦and that’s the event! In the .NET event pattern, the publisher of an event keeps a strong reference to the subscribers. In other words: the publisher of an event keeps its subscribers alive! Where’s the publisher and what is the subscriber in our case? The publisher is obvious, it is the UIButton object. The subscriber is a bit tricky because we use a lambda (we could also use an anonymous delegate and see the same issue). These are sweet but they are syntactical sugar only. Back in the days before those were available, our code would have looked something like this:

{
    ...
    removeImgBtn.TouchUpInside += HandleButtonClicked;
    ...
}

The anonymous delegate or lambda turns into a class scope method:

void HandleButtonClicked(object sender, EventArgs args)
{
    imgView.RemoveFromSuperview();
}

That brings up a problem: we use the imgView reference in the event handler method but it is not accessible, because it is a local variable in ViewDidLoad(), so this requires further refactoring: we must move imgView to class scope. A lot of manual work – why should we do it? Let the compiler do it! And the compiler does it. Remember: if you write an anonymous delegate, the compiler will rewrite it into good old class methods! We can use tools like IL Spy to inspect the generated IL (Intermediate Language) and decompile it. Here’s what the result looks like. It generates nested class inside our view controller:

[CompilerGenerated]
private sealed class c__AnonStorey1
{
    // Compiler generated nested class has reference to our image view.
    internal CustomImageView imgView;

    // Here's the click handler - it was moved into the nested class!
    internal void <>m__0(object sender, EventArgs e)
    {
        this.imgView.RemoveFromSuperview();
    }
}

Next, the compiler will rewrite ViewDidLoad() to make use of the nested class:

public override void ViewDidLoad()
{
    base.ViewDidLoad();

    // Create an instance of the nested helper class. 
    ViewController.c__AnonStorey1 c__AnonStorey2 = new ViewController.c__AnonStorey1();

    // Assign to a temp variable.
    ViewController.c__AnonStorey1 arg_D3_0 = c__AnonStorey2;

    // Here's our image view instance.
    CustomImageView customImageView = new CustomImageView();
    customImageView.set_Frame(new CGRect(0, 100, 320, 100));

    // Now the **local** variable is **copied** into the nested class.
    arg_D3_0.imgView = customImageView;
    base.Add(c__AnonStorey2.imgView);

    // Next is the button that will remove the image view.
    UIButton uIButton = new UIButton(1L);
    uIButton.set_Frame(new CGRect(0, 200, 320, 40));
    uIButton.SetTitle("Remove image", 0L);
    // Now the event handler method of the **nested* class is used for the click handling.
    uIButton.add_TouchUpInside(new EventHandler(c__AnonStorey2.<>m__0));
    base.Add(uIButton);
}

That’s cryptic code. But that’s what is happening behind the scenes and all of this only, to allow the (anonymous) event handler to access our local image view.

Our event subscriber is the nested, compiler generated object. This is what the publisher (= the button) will keep alive through its event. The nested class has a reference to the image view, so the nested class keeps the image view alive. Since the publisher does not go away, the subscriber won’t go away and that means the image view imgView won’t go away. In the previous example, there was only one button instance involved, that’s why it got disposed.

Now let’s get rid of the imgView. We have to break the references. There are multiple ways to do this:

  • Set imgView = nullafter it has been removed from the parent view. Since the nested class points to the same instance, the reference will be gone and the object can be collected. However it will still hang around until the next GC cycle.
  • Dispose it: imgView.Dispose() – this will immediately release it together with the native image data.
  • Remove the button from its parent.
  • Use a WeakReference instead of a strong reference, as shown here in the example below.

By creating a weak reference, the only strong reference will come from the native image view. The managed one is weak and that tells the GC to treat it as if it was not there at all. So if the view gets removed from the parent, the only strong reference will be gone and the view can be collected.

// Create a weak reference.
var weakRef = new WeakReference(imgView);
// Get rid of the strong reference.
imgView = null;
removeImgBtn.TouchUpInside += (sender, e) => {
    CustomImageView target = null;
    if(weakRef.TryGetTarget(out target))
    {
        target.RemoveFromSuperview();
    }
};

4. The not-collected button

We now go two steps back. Our example will be similar to the paragraph “The prematurely collected button” but we’ll add some extra code from “The not-collected image view”. Again, in ViewDidLoad() we create a custom button that removes itself from the parent if clicked (I omitted all the setup code, like setting the title etc) but we will also keep the the image view and the second button that will remove the image view from its parent:

override void ViewDidLoad()
{
    // This button removes itself when clicked.
    var btn = new CustomButton ();
    btn.TouchUpInside += (sender, e) => {
        btn.RemoveFromSuperview();
    };
    this.Add (btn);

    // This image view will be removed by the button below.
    var imgView = new CustomImageView();
    this.Add(imgView);
    var removeImgBtn = new UIButton(UIButtonType.System);
    removeImgBtn.TouchUpInside += (sender, e) => {
        imgView.RemoveFromSuperview();
    };
    this.Add(removeImgBtn);
}

Take a minute to look at the code example and study it.
* The code for the first button is exactly the same we have already used. We learned that the only strong reference to btn is held by the native button instance; that means if the button gets removed from the parent, it’ll be collected. You can try this out in Xamarin Studio to see it for yourself.
* The second part is exactly the code we used for our “remove image view” example: the image view won’t be collected, unless we null it out, dispose it or use a weak reference or remove the button.

If you click the first button now in this example, it will get collected, right? Wrong. It won’t, even though we are using exactly the same code. Again, welcome to the world of anonymous delegates and lambdas! To figure out what’s going on, we must once more analyze the IL. This is what gets generated:

[CompilerGenerated]
private sealed class c__AnonStorey1
{
    // Compiler generated nested class has reference to our first button...
    internal CustomButton btn;
    // ...and an event handler to remove it.
    internal void <>m__0(object sender, EventArgs e)
    {
        this.btn.RemoveFromSuperview();
    }

    // The generated class has a reference to our image view...
    internal CustomImageView imgView;

    // ...and the click handler of the second button to remove the image view.
    internal void <>m__0(object sender, EventArgs e)
    {
        this.imgView.RemoveFromSuperview();
    }
}

I’ll skip the rewritten ViewDidLoad() method. Here’s what happened: the anonymous delegates/lambdas for both, the first button that removes itself and the second button that removes the image view have been moved into the same nested compiler generated class. If we now click the first button, it will be removed from its parent and the strong reference from the native part is gone. But why won’t it be collected? Well, the problem is that we have the second button which is still part of the UI hierarchy. So unmanaged code holds a reference to that button (the one that removes our image view). Since that button has an event and the event uses the same nested class, the nested class will stay alive and with it the reference to the first button, preventing it from being collected.

Now keep on reading that paragraph above until you fully understand it. 🙂

This behavior is somewhat unexpected and if we were to use class methods instead of anonymous delegates, it would be consistent: the button would not be collected in both cases because we’d move it to class scope.

If we want to get rid of the button in this example, we can:

  • Work with weak references
  • Set btn = null after removing it or dispose it
  • Remove the second button from the UI hierarchy

Or we could do something fancy/hacky (depends on our point of view)! 🙂

We have seen that .NET uses the same anonymous nested class for both local variables. Can we maybe force it to generate a different nested class for the second button? In that case, the first one would still be eligible for collection! The compiler generated classes are created “per scope”. That means for everything between ViewDidLoad()‘s opening curly brace and closing curly brace the same class will be used. If we use anonymous delegates in, let’s say, ViewWillAppear() it’ll generate a new nested class. What denotes a scope in C#? The good old curly brace! If we refactor our ViewDidLoad() and add an extra pair of curly braces around the image view part, we can “trick” the compiler:

override void ViewDidLoad()
{
    // This button removed itself when clicked.
    var btn = new CustomButton ();
    btn.TouchUpInside += (sender, e) => {
        btn.RemoveFromSuperview();
    };
    this.Add (btn);

    // Extra curly brace here!
    {
        // This image view will be removed by the button below.
        var imgView = new CustomImageView();
        this.Add(imgView);
        var removeImgBtn = new UIButton(UIButtonType.System);
        removeImgBtn.TouchUpInside += (sender, e) => {
            imgView.RemoveFromSuperview();
        };
        this.Add(removeImgBtn);
    }
}

The “useless” pair of curly braces introduces a new scope and that will generate a separate compiler generated object.

Working with iOS can be tricky. But we have seen that also “regular” .NET has some surprises for us. Be sure to check out the documentation over at Xamarin, the Evolve 2013 talk by Mark Probst and Rodrigo Kumpera, the Xamarin Profiler documentation and the Evolve 2014 presentation about memory management.

Post Media Link

krumelur