I have a MonoTouch app that has a UITabBarController, with each of the tabs being a UINavigationController. Some of these wrap a UIViewController which adds a UITableView and a UIToolbar, and others wrap a DialogViewController.
I've not paid much attention to memory / view management thus far (I've been mostly running in the simulator), but as I've started testing on a real device, I've noticed some failures due to low memory conditions (e.g. the app gets terminated, and I discover from my log that DidReceiveMemoryWarning got called prior to this). Other times I notice prolonged pauses in the app's responsiveness that I am assuming are due to a GC cycle.
Thus far I've been assuming that every DialogViewController that I push onto the nav stack will clean up its views and other things it's allocated when I pop it. But I am starting to realize that it's probably not that easy, and that I need to start calling Dispose() on things.
Are there best practices for how to deal with managing resources and memory with MonoTouch and MT.D? Specifically:
I apologize for the general nature of this question - this seems like a good topic for a whitepaper, but I couldn't find any...
Update: to make the question more concrete: after using Instruments and the Xamarin Heapshot profiler, it's clear to me that I'm leaking UIViewControllers when the user pops the navigation stack. Rolf filed a bug for this and it has two dups, so this is a real issue for more than just me. Unfortunately I haven't found a good workaround for the leaked UIViewControllers - I have not found a good place to call Dispose() on them. The natural place to free resources allocated by ViewDidLoad is in the ViewDidUnload message, but it never gets called on the simulator so my memory footprint keeps growing. On the device, I do see DidReceiveMemoryWarning, but I am reluctant to use this as the place to free my viewcontroller and its resources since I am not guaranteed that iOS will actually unload my view, and therefore not guaranteed that my ViewDidLoad will get called again either (leading to a ViewDidAppear which would need to code defensively against situations where its underlying resources were disposed). I'd love to get some advice on how to get out of this mess...
I've spent a couple of days in the MT.D source code and in the profiler. While I am still looking for general guidance on what the best design pattern is for implementing DidReceiveMemoryWarning and ViewDidUnload, I do have some general observations to share that could be useful for someone:
In general, I adhere to the following template for allocating objects in my view controllers:
There is a general issue with leaking ViewControllers when the user navigates up the nav stack (I reference the bugzilla link in the "Update" in the question). This also affects MT.D. There is a fairly straightforward workaround - add the following line of code in ViewDidAppear of the parent view controller:
// HACK: touch the ViewControllers array to refresh it (in case the user popped the nav stack)
// this is to work around a bug in monotouch (https://bugzilla.xamarin.com/show_bug.cgi?id=1889)
// where the UINavigationController leaks UIViewControllers when the user pops the nav stack
int count = this.NavigationController.ViewControllers.Length;
Rolf does a great job explaining why this bug happens and why the workaround works in the bugzilla link, so I won't repeat it.
I hope someone finds this useful. I also hope someone smarter than me has some guidance on how to handle DidReceiveMemoryWarning and how to split work up between that method and ViewDidUnload.
Update: A couple more notes:
My app has a TabBar where each tab hosts a UINavigationController, most of which push a DialogViewController. One issue I was dealing with was leaking the DialogViewController after the ViewDidUnload let go of the reference to it. I tried Disposing the DVC in ViewDidUnload, but iOS kept on wanting to reinvoke it and I was getting an exception for invoking a selector on a GC'ed object. I discovered the reason - the navigation controller was holding onto the DVC in its ViewControllers array. The solution is to release the array by creating a zero-length array in its place - in ViewDidUnload:
this.ViewControllers = new UIViewController[0];
The old array will now be GC'ed, and so will the DVC because nothing is pointing to it anymore. And iOS won't ever reinvoke the object. Note - no need to call Dispose on the DVC.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With