In iOS6 the method viewDidUnload became deprecated, the memory management has changed a lot for a UIViewController. Here is a little brief about the new patterns.
The part that occupies more memory in a view controller is the view (and of course huge data that you would eventually create). The view itself it doesn't consume so much memory, is the backing store, the part that is drawn (for the most curious is the CABackingStore). This new pattern seems to check as volatile the memory occupied by all the backing stores owned by view controllers' views that are not displayed in a window. When a memory warning comes this backing store will be purged from memory. With this method you can save the process of recreating the view that is quite expensive.
Apple says that is safe to remove viewDidUnload/viewWillUnload from also iOS5 project and even if you set the deployment target to iOS5 the templates don't show those methods. I understand that if the outlets owned by the view are weak, when you release the view controller's view in the superclass implementation everything will be dealloc correctly without leaks or zombies thanks to ARC.
Since I really appreciate this new approach I'm not confident with this kind of situation: let's suppose that we have a view controller and its view, this view is just a content view that will host different views created in the view controller's xib, changing them at runtime dynamically. When you create outlets for this views they are automatically created as strong and that makes sense because the the "main view" doesn't own none o f them. Regarding the new rules the backing store of this views will not be signed as volatile since they are not owned by the main view in iOS6 and in iOS5 (if I remove the viewDidUnLoad) they will be not released for the same reason.How can i manage this situation? It will be correct to release them inside the didReceiveMemoryWarning? but were can I recreate them in iOS6 if the view will be loaded only once ?
Related
When we get low memory warning, we release all the views, set outlets to nil, and recreate them all over again.
but for views added in the interface builder, they are only added when we initWithNibName is called.
So, is it in general bad practice to use a lot of views via interface builder or that its automatically taken care of by original UIViewController implementation of didReceiveMemoryWarning(iOS 6+) or viewDidUnload (iOS 5 and below) ?
The default behavior for a viewcontroller is to load its view hierarchy when the view property is first accessed and thereafter keep it in memory until the view controller is disposed of. There is no indication that a view created using code is different from one created in Interface Builder. I personally prefer InterfaceBuilder owing to app maintenance since UI code gets complex pretty fast and separating UI elements to the functional aspect of UI elements is a clean bifurcation making it easy for a new programmer to maintain your code base.
But when it comes to releasing memory didReceiveMemoryWarning delegate is to be used to explicitly release the view hierarchy if that additional memory is necessary for your app.
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Add code to clean up any of your own resources that are no longer necessary.
if ([self.view window] == nil)
{
// Add code to preserve data stored in the views that might be
// needed later.
// Add code to clean up other strong references to the view in
// the view hierarchy.
self.view = nil;
}
return;
}
note here that we test if the view controller’s view is not onscreen. If the view is associated with a window, then it cleans up any of the view controller’s strong references to the view and its subviews. If the views stored data that needs to be recreated, the implementation of this method should save that data before releasing any of the references to those views. By storing either you can keep it in memory as part of previous viewcontroller's memory or store it in coredate or some other persistent (disk cache etc) way.
The next time the view property is accessed, the view is reloaded exactly as it was the first time. Pictographically it makes perfect sense -
[source]
I decided to use storyboards in a project I have been doing. When the app launches it does the right thing of awakeFromNib and then viewDidLoad, but when the app has finished segueing to another view, it doesn't call viewDidUnload and, I think, neither does dealloc. I have used Apple's Instruments and doesn't show any memory leaking.
Just to note, I am using custom segues and testing this by inserting NSLogs into the methods. Has anyone else come across this?
Just want to update: dealloc actually is called but not viewDidUnload.
The viewDidUnload method is solely for the purposes of didReceiveMemoryWarning (i.e. when the view is being removed to recover some memory, but the view controller is not). If you want to see viewDidUnload while running in the Simulator, push or presentViewController to a secondary view, then generate a memory warning from the Simulator's menus. I quote from the UIViewController Class Reference:
When a low-memory condition occurs and the current view controller’s views are not needed, the system may opt to remove those views from memory. [The viewDidUnload method] is called after the view controller’s view has been released and is your chance to perform any final cleanup. If your view controller stores separate references to the view or its subviews, you should use this method to release those references. You can also use this method to remove references to any objects that you created to support the view but that are no longer needed now that the view is gone. You should not use this method to release user data or any other information that cannot be easily recreated.
At the time this method is called, the view property is nil.
viewDidUnload is called when the view is actually unloaded. If you want to clean up your resources when the view is not displayed put that in viewDidDisappear.
If you want to see what is happening with viewDidUnload, run your app in the simulator and from the menubar choose Hardware | Simulate Memory Warning.
Under memory pressure, views that are not on screen are removed and that is when the viewDidUnload method is sent.
There are a few instances where my app crashes when turned back on from sleep and the app is still open. It happens if a UITableViews is open when the iPhone is put into sleep; the table is being filled from an XML being parsed. I understand why it is crashing; in my viewDidUnload method I am releasing the array that is filling the table. How to handle this I am not sure; yes I could simply not release it in viewDidUnload, but then it would never leave memory if you returned to the main menu.
Any help would be appreciated!
The method viewDidUnload is not the right place to release your data if at all. I quote Apple's documentation, which desribes it better than I could:
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.
ViewDidUnload is used only to release view related objects. A view controller can release its view because its not shown, still your instance of that controller exists and so does your model.
I'm pretty new to programming and I started learning ObjC and the CocoaTouch Framework.
I've learnt about the delegation pattern and I'm pretty comfortable using it, but maybe I'm using it too much.
There is one thing I do not understand and it's specific to UIKit view controllers, I've read a lot of posts about it but I'm not able to find a clear answer.
Memory management with delegates?
Why are Objective-C delegates usually given the property assign instead of retain?
Let's suppose that I have a navigation controller and I'm pushing view controllers through it, imagine that the visible controller has as a delegate (assign not retain) a non-visible controller in the stack. A memory warning is incoming and all the view controllers (except the visible one ) are unloaded using the viewDidUnload and dealloc method, the delegate will be unloaded and the "callback" never sent.
If the new view controller is not pushed but presented modally the "connection" between delegate and the view controller is never lost, viewDidUnload is never called in the parent view.
Here is my question:
Is it correct to use delegation pattern between two view controllers?
It is certainly a correct design, depending on your controllers semantics. I used this when having a "master" view controller managing (and receiving delegate calls) from "slave" view controllers.
As to your analysis of what happens in case a memory warning is sent, there is possibly a slight misunderstanding, in that the view controller actually receives the viewDidUnload, but what this means is that the view controlled by it has been unloaded to get back some memory, so the controller can do its part of cleaning (as usual). The view controller is not itself "unloaded" or released or whatever. So the callback will always be sent. The only thing is that if the view had been previously unloaded, you would need to restore it.
Keep also in mind that, if is utterly impossible for you to recreate your unloaded views, you can prevent a specific view from being released by not calling super in your didReceiveMemoryWarning override. Take this suggestion "cum granum salis", however!
In the end, no need to use a Modal View.
Indeed, this is what Apple Docs say:
(void)didReceiveMemoryWarning :
The default implementation of this method checks to see if the view controller can safely release its view. This is possible if the view itself does not have a superview and can be reloaded either from a nib file or using a custom loadView method. If the view can be released, this method releases it and calls the viewDidUnload method.
(void)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.
Your UIViewController is fairly lightweight and should never be unloaded in a low memory condition. The UIView owned by the view controller is very heavyweight and will definitely be unloaded in low memory conditions. Your controller should be ready to recreate the view if necessary, but you'll never need to regenerate your stack of view controllers.
I am introducing auto-rotation to my app and I'm having an issue with a memory warning. Whatever orientation I start my app in, as long as the device remains in that orientation, I get no memory warnings. However, the first time I rotate the device the following warning is placed on the console: Safari got memory level warning, killing all documents except active. When this happens all view controllers, other than the one be viewed, are unloaded - this produces unexpected behaviors when navigating back to view controllers that should normally already be on the stack. The app never crashes and this warning occurs once upon the first rotation, after that it never happens (until I stop/start the app again). Also, this only happens on the device - no memory warning when running in simulator.
Has anyone seen this behavior? In any case, does anyone have any suggestions on what I might try in order to remove the memory warning.
Thanks in advance.
You can't assume that memory warnings will never happen; you have to handle them gracefully. Suggestions:
Check for memory leaks with Leaks (note that it doesn't catch all leaks).
Fix your view controllers to handle a view reload. Specifically (unless you override -(void)loadView), it'll call -(void)viewDidUnload on a memory warning and -(void)viewDidLoad when it becomes visible again. You might fix this by saving state in the view controller and restoring it to the views in -(void)viewDidLoad.
If you can't be bothered handling the memory warning, implement -(void)didReceiveMemoryWarning and do not super-call (i.e. comment out [super didReceiveMemoryWarning]). This is lazy, and might cause a crash if your app ends up using too much memory (background apps like Safari and Phone will be killed first).
You can test memory warning behaviour with the "simulate memory warning" option in the simulator.
Memory warnings are part of a normal iOS behavior, due to its limited memory, especially now that multi-tasking is supported.
UIKit doesn’t only allow navigation back from a view controller, but also allows navigation to other view controllers from existing ones. In such a case, a new UIViewController will be allocated, and then loaded into view. The old view controller will go off-screen and becomes inactive, but still owns many objects – some in custom properties and variables and others in the view property/hierarchy. And so does the new visible view controller, in regard to its view objects.
Due to the limited amount of memory of mobile devices, owning the two sets of objects – one in the off-screen view controller and another in the on-screen view controller – might be too much to handle. If UIKit deems it necessary, it can reclaim some of the off-screen view controller’s memory, which is not shown anyway; UIKit knows which view controller is on-screen and which is off-screen, as after all, it is the one managing them (when you call presentModalViewController:animated: or dismissModalViewControllerAnimated:). So, every time it feels pressured, UIKit generates a memory warning, which unloads and releases your off-screen view from the view hierarchy, then call your custom viewDidUnload method for you to do the same for your properties and variables. UIKit releases self.view automatically, allowing us then to manually release our variables and properties in our viewDidUnload code. It does so for all off-screen view controllers.
When the system is running out of memory, it fires a didReceiveMemoryWarning. Off-screen views will be reclaimed and released upon memory warning, but your on-screen view will not get released – it is visible and needed. In case your class owns a lot of memory, such as caches, images, or the like, didReceiveMemoryWarning is where you should purge them, even if they are on-screen; otherwise, your app might be terminated for glutting system resources. You need to override this method to make sure you clean up your memory; just remember you call [super didReceiveMemoryWarning];.
An even more elaborate explanation is available here: http://myok12.wordpress.com/2010/11/30/custom-uiviewcontrollers-their-views-and-their-memory-management/