4

The application I'm writing needs to support iOS5+. Recently, Apple obsoleted ViewDidUnload as we're told there is no significant memory gain in releasing views on memory warning.

In my application, I have a UIViewController that manages a very heavy UIWebView.
This view controller is presented modally and, as a result, often being created and dismissed.

By using Instruments, I found out that the memory taken by UIWebView is not being freed immediately after its controller is dismissed.

I assumed that the controller would eventually get collected by Mono GC, and it would call Dispose on the controller, as well as on its view, which would dispose UIWebView and free underlying native object.

I can't test if this is the case: unfortunately after presenting and dismissing the controller for about ten times, I get a memory warning and the app crashes the next second. I'm not sure if Mono GC gets a chance to run at all.

So what I did was adding GC.Collect call right after the controller has been dismissed.
I also had to add ReleaseDesignerOutlets in ViewDidDisappear.

This seems to free UIWebView.

Update: I already found out that ReleaseDesignerOutlets call in ViewDidDisappear was obviously releasing the web view, but there was no benefit to GC call. In fact, GC never collected my controller because a button click handler was keeping the whole controller alive.

Now, I feel completely lost in some kind of Cargo memory management.

  1. Is it reasonably to force garbage collection in my case?
  2. Why do I have to call ReleaseDesignerOutlets? Surely, if there are no references to the “dead” controller, its views should be considered eligible for collection as well?
  3. From Instruments heapshot diff, it looks like the views created from code “hold on” to the controller as well. Do I have to dispose them? Nullify them?
  4. Do I need to manually call Dispose on the controller I just dismissed?
  5. Do I need to include ReleaseDesignerOutlets call in Dispose method of my controller?
  6. Do I need to null out references to child views in my custom UIView subclasses on Dispose?
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • It looks like I'm having some progress now. `navigationBar.TopItem.LeftBarButtonItem` contains a link to a custom button that contains a link to my controller (event handler). Can't GC figure this out? – Dan Abramov Oct 24 '12 at 17:00

2 Answers2

4

You should just call Dispose() on your controller when it is dismissed.

So something like:

private YourModalController modalController;

//When your button is clicked
partial void YourButtonClick() {
  modalController = new YourModalController();
  PresentViewController(modalController, true, delegate {
    modalController.Dispose();
    modalController = null;
  });
}

In YourModalController, make sure you have:

public override void Dispose(bool disposing) {
  ReleaseDesignerOutlets();
  base.Dispose(disposing);
}

You don't necessarily have to worry about ViewDidUnload in this case, since this controller is disposed when dismissed.

Prior to iOS 6:

  • ViewDidUnload was called in a low memory warning for the app
  • on controllers that are still in memory, but not actively on the screen such as down the stack in a UINavigationController
  • On this event, you should dispose any views you have C# references to and set them to null
  • for iOS 6 this doesn't happen any more

Likewise if you have this:

private UIButton buttonIMadeFromCode;

You should check for null, dispose it, and set it to null in Dispose() and ViewDidUnload() (but only mess with ViewDidUnload if you are targeting less than iOS 6).

jonathanpeppers
  • 26,115
  • 21
  • 99
  • 182
  • Thanks for the answer. I'm still in doubts though. Let's imagine for a moment I don't want to `Dispose` the controller from code, and I'm content with GC calling `Dispose` when there are no references to it. Am I right to think that, in this case, there is no benefit from calling `ReleaseDesignerOutlets` in `Dispose`? For, if there are no references to the view controller and its view is not visible, its subviews would get collected and released anyway? Or would they not? – Dan Abramov Oct 24 '12 at 17:20
  • 1
    The point is you shouldn't rely on the `GC` when a explicit route for disposal is present and straightforward. Your app will perform better overall if you manually dispose: less memory usage, less GC collects, etc. If the `GC` falls behind, which can happen, iOS might kill your app for high memory usage. – jonathanpeppers Oct 24 '12 at 17:54
  • Yeah, I see your point and it's the route I will go. I asked because I want to understand how MonoTouch GC and ObjC refcount live together. I think I'm getting close but I'm still not sure—if we never call `Dispose` explicitly and the controller ends up collected, is there any benefit from releasing its outlets? – Dan Abramov Oct 24 '12 at 18:26
  • 1
    Disposing the outlets prevents the `GC` from having to do that work later on. So if you disposed the controller and not the outlets, the `GC` would eventually dispose the outlets for you. – jonathanpeppers Oct 24 '12 at 20:27
1

First: memory management in MonoTouch is a very complex topic, because MonoTouch (which is garbage collected) has to co-exist with ObjectiveC (which is reference counted).

As you have found out by now it is easy to run into cycles, and when these cross the MonoTouch/ObjectiveC boundary, the GC is not able to figure out exactly what's going on and free the entire cycle.

If you're interested in a more in-depth explanation, check this thread out.

Rolf Bjarne Kvinge
  • 19,253
  • 2
  • 42
  • 86
  • Rolf wrote [a great explanation of GC/refcount interplay](http://stackoverflow.com/a/13059140/458193) here. Check it out if you still don't grok it. – Dan Abramov Oct 24 '12 at 23:55