Trying to hunt down the cause of crashing on certain devices. I am noticing that my view controllers are receiving didReceiveMemoryWarning, but NOT viewDidUnload. And according to Apple:
you would not use didReceiveMemoryWarning to release references to view objects, you might use it to release any view-related data structures that you did not already release in your viewDidUnload method. (The view objects themselves should always be released in the viewDidUnload method.)
So,
A: Why is viewDidUnload not called? I can't remove my view objects here if it is never called.
B: If I'm not supposed to remove my view objects in didReceiveMemoryWarning, where else would I do this?
C: Using ARC, should I still need to remove view objects, set arrays to nil, etc?
As the other mentioned viewDidUnload: is deprecated in iOS 6. But as additional information you should know, that it is seldom necessary to unload a UIView since iOS 6 is doing its magic thingie in the background -it is destroying the bitmap layer of the backing CALayer of the view (which is by far the biggest "part" of a UIView). If the view is needed again iOS will call drawRect: where you compose your view and everything will be ok.
For more information read this great article of Joe Conway: ViewController lifecycle in iOS 6
viewDidUnload is deprecated in iOS6. You "can" remove views in didReceiveMemoryWarning if you think it is necessary, but it is left up to you.
This thread may help as well.
viewDidUnload no longer called in ios6
didReceiveMemoryWarning is specifically targeted not to the unloading of a view, but rather for the view controller to release objects which can easily be recreated (i.e. UIIamges and the like). You should not release objects in your view unless they can easily be recreated as necessary.
Related
my code snippet:
- (void)viewDidUnload{
[super viewDidUnload];
self.statusView = nil;
self.tableView = nil;
self.noDataView = nil;
}
In a rare situation, my app crashed in line self.noDataView = nil;. When I debug by po self, it seemed that it's pointing something other than current controller. What is possible reason?
PS:self.tableView's delegate and dataSource is set to self in init method. Does that have any relation to this?
First, [super viewDidUnload] should be used as the last statement. However, that won't fix your error, probably.
The reason for your problem is quite simple. Your controller is overreleased somewhere. Do you have zombie detection enabled? The code where the application crashes is usually irrelevant because the problem happened earlier.
viewWillUnload is deprecated now, you can't count on it anymore, any question about it will lead you to the below references.
From Apple:
In iOS 6, the viewWillUnload and viewDidUnload methods of
UIViewController are now deprecated. If you were using these methods
to release data, use the didReceiveMemoryWarning method instead. You
can also use this method to release references to the view
controller’s view if it is not being used. You would need to test that
the view is not in a window before doing this.
And Quote WWDC 2012:
The method viewWillUnload and viewDidUnload. We're not going to call
them anymore. I mean, there's kind of a cost-benifit equation and
analysis that we went through. In the early days, there was a real
performance need for us to ensure that on memory warnings we unloaded
views. There was all kinds of graphics and backing stores and so forth
that would also get unloaded. We now unload those independently of the
view, so it isn't that big of a deal for us for those to be unloaded,
and there were so many bugs where there would be pointers into.
Edit:
For your problem in iOS 5.1, viewDidUnload is used to release anything you have made when the view was created, so unless you are creating or retaining objects it in viewDidLoad or your nib, you may not release them in viewDidUnload.
In new iOS 6, viewDidUnload is deprecated and we have been instructed to use didReceiveMemoryWarning instead, to manage objects in UIViewController instances and subclasses. Is it equally effective to assign nils to UIView kinds inside didReceiveMemoryWarning like the way it has been done inside viewDidUnload?
I am asking this because these two methods seems to be working differently. It seems like didReceiveMemoryWarning doesn't guarantee viewDidLoad to be called again to re-instantiate any necessary UIViews.
I suspect with iOS 6, memory management is done without requiring to manually deallocate UIView. Please help me to know what I have missed in understanding the lifecycle of UIViewController.
My preferred method is now the following:
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if (self.isViewLoaded && !self.view.window) {
self.view = nil;
}
// Do additional cleanup if necessary
}
Note that the test self.isViewLoaded is essential, as otherwise accessing the view causes it to load - even the WWDC videos tend to miss that.
If your other references to subviews are weak references, you don't have to nil them out here, otherwise you want to set them to nil, too.
You should get rid of viewDidUnload completely, and every code there should move to appropriate places. It wasn't guaranteed to be called prior to iOS 6 before anyway.
In the iOS reference for viewDidUnload:, it states that this is deprecated for iOS 6 because
Views are no longer purged under low-memory conditions and so this
method is never called
It doesn't say anything about placing this code in didReceiveMemoryWarning:. Since views are no longer purged under low memory conditions, you never have to worry about cleaning up your views in either method.
The answer by Eiko is not correct, and we should NOT set self.view to nil when receiving low memory warning. Doing so is useless and may be harmful.
iOS 6 will automatically freeing bitmaps of views which is not currently displayed, See http://thejoeconwayblog.wordpress.com/2012/10/04/view-controller-lifecycle-in-ios-6/ for details.
I just upgraded to Xcode4.5. When I ran the unit tests, I realised that [view setbackgroundcolor] does not call [viewdidload].
Did anyone had the same issue?
This is some undocumented behavior which you should not rely upon, because it (will break your code one day) just broke your code...
Are you using iOS6?
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
Prior to iOS 6, when a low-memory warning occurred, the UIViewController class purged its >views if it knew it could reload or recreate them again later. If this happens, it also calls >the viewWillUnload and viewDidUnload methods to give your code a chance to relinquish >ownership of any objects that are associated with your view hierarchy, including objects >loaded from the nib file, objects created in your viewDidLoad method, and objects created >lazily at runtime and added to the view hierarchy. On iOS 6, views are never purged and these >methods are never called. If your view controller needs to perform specific tasks when memory >is low, it should override the didReceiveMemoryWarning method.
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.
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).