Prior to iOS 6, we were supposed to
- (void)viewDidUnload {
self.someDelegate = nil;
[super viewDidUnload];
}
Now that viewDidUnload is deprecated, where do we set our delegates to nil? Thanks!
Views are no longer unloaded when receiving a memory warning in iOS6. So the view is never unloaded on such cases and viewDidUnload is never called in iOS6.
If you really want to mimic the old behavior (unloading the view upon receiving a memory warning) you now have to implement this behavior in the didReceiveMemoryWarning method of your view controller, after testing that the self.view.window property is nil (meaning the view is not on screen anymore, thus will be "unloaded", meaning that you are in the same situation as the old viewDidUnload case).
-(void)didReceiveMemoryWarning
{
if (self.isViewLoaded && !self.view.window)
{
// If view already loaded but not displayed on screen at this time (not attached to any window) then unload it
self.view = nil;
// Then do here what you used to do in viewDidUnload
self.someDelegate = nil;
...
}
[super didReceiveMemoryWarning];
}
But note that if you use ARC and iOS5+, you generally won't need to set your delegate to nil anymore, thanks to the weak property attribute and the Zeroing-Weak-References mechanism that automatically reset weak variables and properties to nil if the object they are pointing to does not exist anymore (thus avoiding dangling pointers).
[EDIT] And as explained by #Martin R in the comments, views are not unloaded anymore when receiving a memory warning in iOS6, so you won't have to manage this case of receiving a memory warning and think about releasing your delegate there as this use case won't occur anymore in iOS6.
Well, I did not come up with this myself, but if should help you: "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."
http://www.bgr.com/2012/06/11/ios-6-beta-download-link-iphone-ipad-ipod-touch-release/
Prior to iOS 6, we were supposed to
(void)viewDidUnload {
self.someDelegate = nil;
[super viewDidUnload];
}
Where did you hear this?
Do you even know what viewDidUnload is for prior to iOS 6?
viewDidUnload is only called in low memory situations, which cause the view to be unloaded. It will never be used during normal operation. If you were depending for it to be called to do other things, that is wrong.
Plus, why would you ever need to set self's delegate to nil anyway? "Niling the delegate" refers to setting other objects' delegates (which point to self) to nil when self is deallocated. Setting self's delegate makes no sense (why would you care setting stuff on self if self is no longer used?).
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.
I simply created a new subclass of UIViewController.
Added some UI objects on it in interface builder, and
created outlets for these UI objects and made respective connections.
Then, suddenly, I saw this code generated automatically for me in the
implementation of my ViewController:
- (void)viewDidUnload {
imageView = nil;
scrollView = nil;
[super viewDidUnload];
}
I didn't notice this happening before, was it supposed to happen like this? why?
I don't recall it getting auto-generated, but viewDidUnload is deprecated in iOS 6 so you can just delete it (assuming you're targeting 6.0+).
The purpose of viewDidUnload method is to release the resource whatever you have created in the viewDidLoad method so it is doing exact opposite to the viewDidUnload method . Xcode provides the facility to release the allocated resource automatically so you are finding that code into the viewDidUnload method. For more information you can follow the following link:
viewDidUnload refernce.
It's called when Received memory warning.
- (void)viewDidUnload NS_DEPRECATED_IOS(3_0,6_0); // Called after the view controller's view is released and set to nil. For example, a memory warning which causes the view to be purged. Not invoked as a result of -dealloc.
By the ways, If you want release sth. when recevied memory warning, you can use
- (void)didReceiveMemoryWarning; // Called when the parent application receives a memory warning. On iOS 6.0 it will no longer clear the view by default.
So with viewDidUnload deprecated as of iOS 6, what do I need to do now?
Delete it, and migrate all of it's contents in didReceiveMemoryWarning, or leave it, and don't do anything in didReceiveMemoryWarning?
The short answer is that, in many cases, you don't need to change anything. And, you most certainly do not want to simply migrate all of the contents of viewDidUnload to didReceiveMemoryWarning.
Generally, most of us do the setting of IBOutlet references to nil in viewDidUnload (largely because Interface Builder would put that there for us) and do the general freeing of memory (e.g. clearing of caches, releasing of easily recreated model data, etc.) in didReceiveMemoryWarning. If that's the way you do it, then you probably don't require any code changes.
According to the iOS 6 viewDidUnload documentation:
Views are no longer purged under low-memory conditions and so this method is never called.
Therefore, you do not want to move the setting of your IBOutlet references to nil anywhere, because the views are no longer purged. It would make no sense to set them to nil in didReceiveMemoryWarning or anything like that.
But, if you were responding to low memory events by releasing easily-recreated model objects, emptying caches, etc., in viewDidUnload, then that stuff should definitely move to didReceiveMemoryWarning. But then, again, most of us already had it there already.
Finally, if you free anything in didReceiveMemoryWarning, just make sure your code doesn't rely upon them being recreated in viewDidLoad again when you pop back, because that will not be called (since the view, itself, was never unloaded).
As applefreak says, it depends upon what you were doing in viewDidUnload. If you update your question with explicit examples of what you had in your viewDidUnload, we can probably provide less abstract counsel.
The short answer:
Never use -didReceiveMemoryWarning for a balanced tear down as it might get called never or multiple times. If you have your setup in -viewDidLoad, place your cleanup code in -dealloc.
The long answer:
It's not easy to give a general answer, as it really depends on the situation. However, there are two important facts to state:
1. -viewDidUnload is deprecated and in fact never called starting with iOS6 and later. So, if you have your cleanup code in there, your app leaks under these OS versions
2. -didReceiveMemoryWarning might be called multiple times or never. So it's a really bad place for a balanced teardown of objects you created somewhere else
My answer is looking at the common case where you are using properties, for example:
#property (strong) UIView *myCustomView // <-- this is what I'm talking about
#property (assign) id *myDelegate
Here you have to do some cleanup, because you either created and own the customView or InterfaceBuilder created it, but you are retaining it. Before iOS 6 you would probably have done something like this:
- (void)viewDidLoad {
self.myCustomView = [[UIView alloc] initWithFrame:…];
}
- (void)viewDidUnload { // <-- deprecated!
[myCustomView removeFromSuperView];
self.myCustomView = nil;
}
...because (again) myCustomView is a retained property, created and owned by you and so you have to take care and "release" it (set it to nil) at the end.
With iOS 6, the best place to replace -viewDidUnload and to set the retained property to nil is probably -dealloc. There's also viewWillAppear and viewDidDisappear, but these are not tied to the lifecycle of your view/controller, but the display cycle (On the other hand, the -...appear methods are perfect for un-/registering notification listeners!). So it might not be appropriate to create and destroy views before and after every display. dealloc is the only method we can be sure of to be called at the very end of the controller's lifecycle. Note that you must not call [super dealloc] if you're using ARC:
- (void)dealloc {
self.myCustomView = nil;
}
However, if you're using viewDidLoad to do some view related setup that can be freed upon low memory conditions, the other posts showing how to deal with low memory situations are totally valid. In this case you can also use dealloc, but you have to check if your views are still there.
The Lifecycle of a ViewController
Maybe it's also helpful to look a the general lifecycle of a ViewController:
This is the lifetime of a viewController (lines in italic mean that these methods might be called multiple times):
init: ViewController loaded, no interface element (IBOutlet) available yet (all nil)
viewDidLoad: the nib/storyboard has been loaded and all objects are available. The user sees nothing yet
viewWillAppear: the view is about to be displayed
viewDidAppear: the view is on screen
viewWillDisappear: the view is about to go away
viewDidDisappear: the view just was taken off the window
viewDidUnload: NEVER CALLED in iOS6/7
didReceiveMemoryWarning: You don’t know if, when and how often this is called. Prior to iOS6 it might unload the view, after iOS6 it just purges an offscreen cache or does nothing
dealloc: the viewController is about to get destroyed
So, to sum it up there are various possibilities; what goes where now really depends on what was initialized where:
-dealloc if created in -init: or -viewDidLoad:
-viewWill/DidDisappear (paired with -viewWill/DidAppear)
-didReceiveMemoryWarning (might or might not be called)
If you need to know if your UIViewController is dismissing you can add this code to your viewWillDisappear:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if ([self isBeingDismissed] || [self isMovingFromParentViewController])
{
// Do your viewDidUnload stuff
}
}
It is not called at the same time as viewDidUnload did once in the view controller life cycle, but works in most cases for your needs!
Depends on what you do in viewDidUnload but you can use didReceiveMemoryWarning or dealloc to release data. See this.
In most typical cases, this method can be used in place of the old viewDidUnload.
// The completion handler, if provided, will be invoked after the dismissed controller's viewDidDisappear: callback is invoked.
- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^)(void))completion NS_AVAILABLE_IOS(5_0);
Swift 2017 syntax:
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
if (yourChildViewController != nil) {
print("good thing we did this!")
}
yourChildViewControllerProperty = nil
super.dismiss(animated: flag, completion: completion)
}
viewDidUnload is no longer called in iOS6, so as a workaround for an app that does some necessary things in viewDidUnload I have done this:
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// only want to do this on iOS 6
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 6.0) {
// Don't want to rehydrate the view if it's already unloaded
BOOL isLoaded = [self isViewLoaded];
// We check the window property to make sure that the view is not visible
if (isLoaded && self.view.window == nil) {
// Give a chance to implementors to get model data from their views
[self performSelectorOnMainThread:#selector(viewWillUnload)
withObject:nil
waitUntilDone:YES];
// Detach it from its parent (in cases of view controller containment)
[self.view removeFromSuperview];
self.view = nil; // Clear out the view. Goodbye!
// The view is now unloaded...now call viewDidUnload
[self performSelectorOnMainThread:#selector(viewDidUnload)
withObject:nil
waitUntilDone:YES];
}
}
}
Is there any precedent for Apple rejecting something like this? Due to time constraints I can't risk them rejecting anything.
There's no reason why Apple would reject it, but they removed it for a reason.
Calling it manually is just asking for problems. I strongly recommend to get the application logic right and do it the "correct" way.
Edit: After looking over that code again, I have serious doubts about your implementation. The whole construct with calling selectors (we should already be on the main thread!) and removing from superview (which just steals the view from it's owner without telling it) just cannot be correct. This is the type of code that you really want to eliminate from your code base.
There's no reason they would reject this. By deprecating the method, -[UIView viewDidUnload] simply becomes like any other method. You can treat it as if it never existed on UIView in the first place and you just happened to create a method called -viewDidUnload.
It would be a problem if -viewDidUnload still existed internally (which it might) and you attempted to call Apple's (now private) implementation instead of your own but I highly doubt Apple would do this. Just remember in your -viewDidUnload to remember to ask if the super class implements the method before attempting to call it on super if that's what you're currently doing, using:
if ([[self superclass] instancesRespondToSelector:#selector(viewDidUnload)]) {
[super viewDidUnload];
}
If you really wanted to be safe you could always move your code to a different method. Inside viewDidUnload just call your new method for iOS 5 devices and in -didReceiveMemoryWarning call your new method if you're on iOS 6.
I'm not going to comment in length on the logic in your didReceiveMemoryWarning since that wasn't in the question but I will say that you should be very careful about the state you're putting the controller in (make sure those if statements cover all of your bases!) And of course, you cannot expect your view controller and its views to be in the same state when viewDidUnload is called by you as when it was called by UIKit in iOS 5.
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.