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:
Is it required to call Dispose on a DialogViewController after it's popped? If so, where is it best to do this? (ViewDidUnload? DidReceiveMemoryWarning? destructor?)
Does the DVC automatically dispose objects like the RootElement that is passed to it or do I need to worry about this? How about UIImages that it loads as part of rendering a table cell (e.g. StyledStringElement)?
Are there places where I should call GC.Collect() to better space out collections so as to not take a bit hit in responsiveness when a GC does happen?
Does the generational garbage collector help with the interactivity problems and is it stable enough to use in a production app? (I believe it's still billed as "experimental" in MonoDevelop 3.0.2 / MT 4.3.3)
What do I need to do in DidReceiveMemoryWarning to reduce the likelihood that iOS will shoot my app? Since each non-visible view controller seems to get this call, I'm assuming that I should clean up that view controller's resources... should I do the same kinds of things I do in ViewDidUnload?
I don't seem to get my ViewDidUnload called (even after I get a DidReceiveMemoryWarning). In fact I don't recall ever seeing it in my log. If iOS always called my ViewDidUnload after DidReceiveMemoryWarning, I could just do all the cleanup in ViewDidUnload... What is the best way to split cleanup responsibility between ViewDidUnload and DidReceiveMemoryWarning?
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:
MonoTouch.Dialog is very well behaved. It does not leak any resources under ordinary usage. It keeps a control tree under DVC.Root, and each Element's Dispose method correctly Disposes the underlying UIKit control. You don't even have to worry about disposing an old RootElement if you've replaced DVC.Root - the property setter automatically disposes it for you. Overall, MT.D doesn't appear to suffer from any significant memory issues. There is one exception - see below.
When creating your own custom Elements (e.g. MultilineEntryElement), make sure to override the Dispose(bool) method, disposing the underlying UIKit control (e.g. UITextView), and chain the base class Dispose() method. The source code in Miguel's MT.D github project provides plenty of good examples. All the Elements implement the standard Dispose pattern (although they omit a destructor/finalizer that calls Dispose(false)).
When implementing custom view controllers, it is generally not necessary to implement Dispose on UIViewController subclasses, nor on TableView DataSource or Delegate classes. When the view controller gets GC'ed, it will correctly call Dispose on its references. All the cells that you allocate in the DataSource will be properly disposed.
As an exception to (3) - I encountered a nasty issue when adding my own subview to a TableView's cell. This subview is a control I created called "UICheckbox" that ultimately inherits from UIImageView, which has two UIImages (on and off) and a public event called Clicked. I only experience an issue when an event handler which references members of the DataSource is hooked to this event (if the event handler doesn't reference the DataSource or controller itself, all is well). However, when the conditions above are met, and the controller is dismissed, there is apparently some cycle that the GC can't figure out, and every UICheckbox I put on the TableView is leaked (along with its images). The only way I found to work around this was to add code to ViewDidDisappear to dispose of the ViewController and clean up its state IFF it is no longer anywhere in the navigation stack. It's hacky but it works.
In general, I adhere to the following template for allocating objects in my view controllers:
allocate nothing in the constructor (use it only to pass state in)
create a control tree in ViewDidLoad (and dispose it in ViewDidUnload). think "InitializeComponent" in XAML (if that helps). If the UIViewController is going to push a DialogViewController onto the nav stack, the ViewDidLoad is a good place to create the DVC.
initialize values in the control tree in ViewDidAppear. E.g. you can add/delete/replace Elements, Sections, and even the Root of the DVC in this method. But don't create a new DVC.
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:
I now realize the protocol for DidReceiveMemoryWarning and ViewDidUnload: the former is always delivered to every view controller, while the latter is only sent for view controllers that aren't currently displaying, AND aren't deeper than the root of the navigation stack. In the end, I decided to ignore DidReceiveMemoryWarning because I don't really have images that I cache and can dump (as per the iOS guidance). In ViewDidUnload, I release all the resources I allocated in ViewDidLoad.
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.
Related
I am really struggling in understanding what to do in didReceiveMemoryWarning. From what I read on StackOverflow and blogs, the following is my understanding -
Generate all data that your view needs in viewDidAppear and destroy those (set to nil) in didReceiveMemoryWarning. This sounds good to me because those properties can be recreated in viewDidAppear.
However, the problem is that didReceiveMemoryWarning is also called for the view which is currently visible. In this case, obviously I would not deallocate data for the view. Shouldn't didReceiveMemoryWarning NOT be called for the view which is currently visible. But that is not case - How can one handle this?
The Idea with didReceiveMemoryWarning is that any Data that you have that can be re-created, should be deallocated and created from that point on whenever the user needs it. Traditionally, this doesn't include UI components.
So unless you have a ton of UIControls on your screen, it's probably not worth it to write the code to re-cycle, or recreate them (especially since this is done for you in UICollection's and UITableView's). That said, if you're getting a didReceiveMemoryWarning, it's probably because you're keeping some stuff in memory around that you don't need.
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.
Is it reasonably to force garbage collection in my case?
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?
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?
Do I need to manually call Dispose on the controller I just dismissed?
Do I need to include ReleaseDesignerOutlets call in Dispose method of my controller?
Do I need to null out references to child views in my custom UIView subclasses on Dispose?
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).
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.
Does UIImageView automatically release image resources when it isn't in view? And perhaps when the app gets a memory warning?
If not, I'm considering writing a subclass (e.g. SmartReleaseImageView) that does so, to be used in particular situations where this would be beneficial. I was thinking of behaviour along these lines:
When SmartReleaseImageView receives setImage message, it registers itself with a manager e.g. SmartReleaseImageViewManager.
When app gets a memory warning, SmartReleaseImageViewManager loops through all its SmartReleaseImageViews and checks whether any of them aren't added to a superview, or checks whether any of them have a frame that is culled due to being outside of the bounds of the main window.
When SmartReleaseImageView detects that it has come into view and has been released, it loads the image again. So perhaps, it would need to have a method like setImageURL rather than setImage in the first place.
Is this kind of behaviour already built into UIImageView? Or does it exist in a 3rd party version anywhere? Or is there a problem with idea?
(I'm writing a UIImageView-heavy app, and it would be nice to have a global catch-all solution that would help with the memory situation, rather than adding lots of extra code to my UIViewControllers.)
EDIT: Answers so far suggest that UIViewController should be responsible. This is true, and I guess my particular case is atypical. In my case, the UIViewController can contain a lot of custom views (with their own hierarchy) that it doesn't necessarily know the structure of, and which go in and out of view frequently. So, it's difficult for it to know when to release its resources. But yes, perhaps I should find a way for it to deal with the problem itself...
Well, you actually have this automatic management implemented in the UIViewController. Since UIImageView is a subclass of UIView, your view controller should be able to manage it automatically.
Memory Management
Memory is a critical resource in iOS, and view controllers provide
built-in support for reducing their memory footprint at critical
times. The UIViewController class provides some automatic handling of
low-memory conditions through its didReceiveMemoryWarning method,
which releases unneeded memory. Prior to iOS 3.0, this method was the
only way to release additional memory associated with your custom view
controller class but in iOS 3.0 and later, the viewDidUnload method
may be a more appropriate place for most needs.
When a low-memory warning occurs, the UIViewController class purges
its views if it knows it can reload or recreate them again later. If
this happens, it also calls the viewDidUnload method to give your code
a chance to relinquish ownership of any objects that are associated
with your view hierarchy, including objects loaded with the nib file,
objects created in your viewDidLoad method, and objects created lazily
at runtime and added to the view hierarchy. Typically, if your view
controller contains outlets (properties or raw variables that contain
the IBOutlet keyword), you should use the viewDidUnload method to
relinquish ownership of those outlets or any other view-related data
that you no longer need.
source: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
Short answer:
UIImageView does not release resources, never.
But all your views (including UIImageView) are released when the system needs more memory. Note that you have to release your views in [UIViewController viewDidUnload].
Edit:
Unfortunately, it doesn't work when the controller is not displayed. It that case I suggest to purge the components manually in viewDidDissappear (call viewDidUnload manually and remove components from the view hierarchy).
Anyway, this won't help if one view controller has too many images in its hierarchy. In this case I would recommend to create a set of UIImageViews and reuse them, in the same way as UITableView reuses its cells.
I'm creating an application which features having multiple animations.
There are 50 pages and on each page there is a different animation and each animation uses many images.
I'm using UIPresentModelViewController for presenting the views and
am changing images using NSTimer.
When I swipe continuously the application crashes with this message:-
Program received signal: “0”.
Data Formatters temporarily unavailable, will re-try after a 'continue'.
(Unknown error loading shared library "/Developer/usr/lib/libXcodeDebuggerSupport.dylib")
I searched a lot but couldn't find any proper solutions to this issue.
Just check within your code that you are making some mistake by adding new view every time but forgot to release it...
You need to look at (and perhaps post) the stack trace when you crash. And the code that changes the image. This sounds like memory bloat (not a true leak in that someone is still referring to memory). The Analyze menu item might catch something (and you should definitely run it), but you may need to run the Allocation instrument and look at heap checks. See http://www.friday.com/bbum/2010/10/17/when-is-a-leak-not-a-leak-using-heapshot-analysis-to-find-undesirable-memory-growth/ for more.
This sounds like a stack overflow to me. In the "Other C Flags" section of the project's C/C++ language settings add a flag for "-fstack-check" and see if that turns up any unwanted recursion.
Signal 0 is usually due to memory low as the app using too much memory. Check whether memory warning method is called or not.
The data formatter thingy failed to load might be due to there's not enough memory to load it..
50 views like you describe sounds like a big memory hog. So I suspect memory management is unloading some views. Then when your program needs the views, they are not there and your program crashes. The error message doesn't quite fit this, but it may not be accurately telling you what the problem is.
Consider the following possible scenario, and see if it fits how you coded this program.
In order for the OS to manage memory, it can unload views and reload them as needed. When this is done, the methods viewDidUnload, loadView, and viewDidLoad are called.
viewDidUnload:
This method is called as a counterpart to the viewDidLoad method. It is called during low-memory conditions when the view controller needs to release its view and any objects associated with that view to free up memory. Because view controllers often store references to views and other view-related objects, you should use this method to relinquish ownership in those objects so that the memory for them can be reclaimed. You should do this only for objects that you can easily recreate later, either in your viewDidLoad method or from other parts of your application. You should not use this method to release user data or any other information that cannot be easily recreated.
loadView:
The view controller calls this method when the view property is requested but is currently nil. If you create your views manually, you must override this method and use it to create your views. If you use Interface Builder to create your views and initialize the view controller—that is, you initialize the view using the initWithNibName:bundle: method, set the nibName and nibBundle properties directly, or create both your views and view controller in Interface Builder—then you must not override this method.
Check the UIView Class Reference --
viewDidLoad:
This method is called after the view controller has loaded its associated views into memory. This method is called regardless of whether the views were stored in a nib file or created programmatically in the loadView method. This method is most commonly used to perform additional initialization steps on views that are loaded from nib files.
You may have inadvertently initialized these views in your init methods rather than in your loadView methods. If you did this, then when the OS unloads a view (you will see viewDidUnload is called) the memory associated with the view and all subviews (all of the images and animations) will be unloaded. This saves memory, but when you need one of those unloaded views to reappear, loadView will be called first if the view had been previously unloaded. If your view setup is done in the init methods rather than in loadView, then the view will not be setup again. But if the view setup is done in loadView method, it can be recovered after memory management unloads it.
There is one and easy way to find out leaks that is hard to find via leaks instruments and so on - Zombies analyser. It shows every unlinked memory in your program, and you can easily detect leaks and optimize code in minutes.
If you're using lots of images for a single animation you're doing it wrong. You should have one, or several very large images and then just show a portion of that image. This way you can load very few images, but have the same affect of having many images.
Look into cocos2d or other frameworks that are popular for making games, as they will be much more efficient for animations than just UIKit.
Find out the reason for memory crash using instrument tool and then refactor the code with best practises with recommended design pattern. There is no unique solution for this. Thanks.
When my iPhone app receives a memory warning the views of UIViewControllers that are not currently visible get unloaded. In one particular controller unloading the view and the outlets is rather fatal.
I'm looking for a way to prevent this view from being unloaded. I find this behavior rather stupid - I have a cache mechanism, so when a memory warning comes - I unload myself tons of data and I free enough memory, but I definitely need this view untouched.
I see UIViewController has a method unloadViewIfReloadable, which gets called when the memory warning comes. Does anybody know how to tell Cocoa Touch that my view is not reloadable?
Any other suggestions how to prevent my view from being unloaded on memory warning?
Thanks in advance
Apple docs about the view life cycle of a view controller says:
didReceiveMemoryWarning - The default
implementation releases the view only
if it determines that it is safe to do
so
Now ... I override the didReceiveMemoryWarning with an empty function which just calls NSLog to let me know a warning was received. However - the view gets unloaded anyway. Plus, on what criteria exactly is decided whether a view is safe to unload ... oh ! so many questions!
According to the docs, the default implementation of didReceiveMemoryWarning: releases the view if it is safe to do (ie: superview==nil).
To prevent the view from being released you could override didReceiveMemoryWarning: but in your implementation do not call [super didReceiveMemoryWarning]. That's where the view is released by default (if not visible).
The default didReceiveMemoryWarning releases the view by calling [viewcontroller setView:nil], so you could override that instead.
What appears to be working for me was to override setView: to ignore setting to nil. It's kludgy, but then, this is a kludgy issue, and this did the trick:
-(void)setView:(UIView*)view {
if(view != nil || self.okayToUnloadView) {
[super setView:view];
}
}
Could it be so simple?
Even though nowhere in the documentation this is mentioned, it seems that if I exclusively retain my view in viewDidLoad, then it does not get released on Memory Warning. I tried with several consecutive warnings in the simulator and all still seem good.
So ... the trick for the moment is "retain" in viewDidLoad, and a release in dealloc - this way the viewcontroller is "stuck" with the view until the time it needs to be released.
I'll test some more, and write about the results
I don't think any of these ideas work. I tried overriding [didReceiveMemoryWarning], and that worked for some phones, but found one phone unloaded the view BEFORE that method was even called (must have been in extremely low memory or something). Overriding [setView] produces loads of log warnings so I wouldn't risk that by Apple. Retaining the view will just leak that view - it'll prevent crashes but not really work - the view will replaced next time the controllers UI is loaded.
So really you've just got to plan on your views being unloaded any time they're off-screen, which is not ideal but there you go. The best patterns I've found to work with this are immediate commit so your UI is always up-to-date, or copy-edit-copy, where you copy your model to a temporary instance, populate your views and use immediate commit with that instance, then copy the changes back to your original model when the user hits 'save' or whatever.
Because the accepted solution has problems with viewDidUnload still getting called even though the view was blocked from being cleared, I'm using a different though still fragile approach. The system unloads the view using an unloadViewForced: message to the controller so I'm intercepting that to block the message. This prevents the confused call to viewDidUnload. Here's the code:
#interface UIViewController (Private)
- (void)unloadViewForced:(BOOL)forced;
#end
- (void)unloadViewForced:(BOOL)forced {
if (!_safeToUnloadView) {
return;
}
[super unloadViewForced:forced];
}
This has obvious problems since it's intercepting an undocumented message in UIViewController.
progrmr posted an answer above which recommends intercepting didReceiveMemoryWarning instead. Based on the stack traces I've seen, intercepting that should also work. I haven't tried that route though because I'm concerned there may be other memory cleanup which would also be blocked (such as causing it to not call child view controllers with the memory warning message).