Does anyone have an official reference for why methods such as 'viewWillDisappear' should not be called directly? There are several existing posts on the subject but no official link, only opinions.
It does not make sense to do so as it is calling a method "out of cycle" from the lifetime management of a view. They can be, and in many cases are, overridden of course.
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/RespondingtoDisplay-Notifications/RespondingtoDisplay-Notifications.html
The issue in question is for some code I encountered where 'viewWillDisappear' is being called from some method. It is really the content of the 'viewWillDisappear' method that is needed to be called.
Example:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// The following two methods are the ones that need to be called below
[self someMethod];
[self anotherMethod];
}
- (void)delegateMethod
{
[self viewWillDisappear:YES];
// Do some other work
// View is moved off-screen, not deallocated, and therefore, does not "disappear"
}
Instinctively it appears wrong to call any of the view hierarchy methods directly (e.g. viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear). If you tell 'self' and the 'super' view that viewWillDisappear it may do something in the framework that could cause problems later on. I think these methods should be called by the framework only. However, this is my opinion and not an official source. The header files don't seem to provide anything about this.
Any help appreciated.
There is no technical reason you can't.
But you shouldn't.
It's bad coding practice and you might confuse the reader or, worse, make him/her not trust you.
Good coding practice would be the following:
- (void)delegateMethod
{
[self doCommonWork];
}
- (void)viewWillDisappear:(BOOL)animated
{
[self doCommonWork];
}
- (void)doCommonWork
{
// …
}
Related
As seen in this stackoverflow answer about
"NSNotificationCenter with respect to ViewWillAppear and
ViewWillDisappear"
We are pointed in the direction of the below as the preferred approach
"Registering the notification in viewWillAppear and unregistering it
in viewWillDisappear seems to be a clean and symmetric solution to
me."
This is approach is also suggested in this other stackoverflow answer.
My Question is twofold
Why does Apple's AVCam Sample Code in "AAPLCameraViewController.m" removeObservers in viewDidDisappear and not viewWillDisappear as the above answers suggested.
Why do they utilise addObservers after [super viewWillAppear:animated];
while they removeObservers before [super viewDidDisappear:animated];
Code Extracted from "AAPLCameraViewController.m"
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
dispatch_async(self.sessionQueue, ^{
switch (self.setupResult) {
case AVCamSetupResultSuccess: {
// Only setup observers and start the session running if setup succeeded.
[self addObservers];
[self.session startRunning];
self.sessionRunning = self.session.isRunning;
break;
}
}
- (void)viewDidDisappear:(BOOL)animated {
dispatch_async(self.sessionQueue, ^{
if (self.setupResult == AVCamSetupResultSuccess) {
[self.session stopRunning];
[self removeObservers];
}
});
[super viewDidDisappear:animated];
}
There is no single rule/way to register for listening to an event and unregister for the same. It totally depends on the requirements and program flow.
As for your questions:
1)Why does Apple's AVCam Sample Code in "AAPLCameraViewController.m"
removeObservers in viewDidDisappear and not viewWillDisappear as the
above answers suggested.
The Apple's sample code basically deals with the user interactions. So it needs to observe the events when user can actually see the view and interact with it. The observer is removed in viewDidDisappear as it is certain at this point of time that the user would not be able to interact with the view anymore. viewWillDisappear defines a state in view-controller's lifecycle that the view is about to be removed but hasn't been removed yet.So, in theory it's better to stop listening to an event when it would not occur anymore(Here viewDidDisappear). Although, the code might work even if the event is unregistered in viewWillDisappear, but , in certain cases, the coder might have put a lot of code on main thread in viewWillDisappear and stopped listening to events. In this case, some events might be missed.
2)Why do they utilise addObservers after [super
viewWillAppear:animated]; while they removeObservers before [super
viewDidDisappear:animated];
The observer is added on viewWillAppear because the event lifecycle for the event is gonna start(User interacting with view). It does not really matter whether event is registered before/after [super viewDidDisappear:animated]. According to the programming architectures followed in objective oriented languages, the cleanup of child classes precedes it's parent/super classes. So, the cleanup(removing observer) in child is done before cleanup in Parent. Adding observer in viewWillAppear and removing in viewDidDisappear ensures that observer is added before event started and ended after the event ended(What the program requires).
What i consider for NSNotificationCenter implementations?
Life-cycle of event that is gonna need observers.
When to register for an event?(Before event is about to start)
When to unregister for an event?(After event has ended).
Any cleanup needed after unregistering events.
**There a few other AVFoundation specific reasons for adding on viewWillAppear, but the explanation above should sum up the crux.
Where you add and remove the observers depends on your needs. In many cases you would want to add the observer in one of the init... methods or viewDidLoad and remove it in dealloc.
Regardless, there should be some sort of symmetry so it is removed once for each time it is added.
There's really no practical difference between adding in viewWillAppear and viewDidAppear and there's really no practical difference between removing in viewWillDisappear and viewDidDisappear. It will be a rare notification where those differences matter. Same for whether the addition or removal is done before or after the call to [super ...].
Apple's example is a good model to follow. Things are setup before the view is displayed and cleaned up after the view is gone. But again, it really depends on what notification you are dealing with and when you want to listen for the notification.
Ask yourself when you need to know about the notification. Is it during the entire lifetime of the view controller? Is it only while it's visible? Is it from just before it's visible to just after it's not?
The answer to that question determines the best methods to add and remove the observer.
A case can be made that viewWillAppear and viewDidDisappear are the balanced pair not viewWillAppear and viewWillDisappear. The thinking is to set up in viewWillAppear, before the view is visible, and to clean up in viewDidDisappear after it's removed. Your code will be in place for the entire duration that the view is visible.
The sample code attempts to create and remove observers when the controller's view is not yet visible to the user (hence the before appear and after disappear) so as to enhance the user's experience (avoid blank screens, for instance, or redraw while the view is visible).
As for the second question, it appears to be mostly a preference. I do the same: a subclass lets the parent initialize first, and de-initialize last.
I have always thought that calling [super viewWillAppear:animated] is mandatory. But today I came across a piece of code from Apple's example application called CoreDataBooks and here's viewWillAppear: implementation from there:
- (void)viewWillAppear
{
[self.tableView reloadData];
}
Note there is no call to super. This confuses me a lot. If even Apple's code doesn't include call to [super viewWillAppear:] then maybe I can always omit it? Or maybe there are certain rules about this matter? Can anybody explain it?
Even though not calling the super implementation can be harmless sometimes, you should ALWAYS call [super viewWillAppear:], regardless of the bad examples Apple publishes. Not doing it may lead to very nasty issues to track down.
According to the documentation
If you override this method, you must call super at some point in your implementation.
The specific example you mentioned is broken in a more subtle way: they implemented viewWillAppear instead of viewWillAppear:, so that piece of code is not even being executed!
in short: always.
if you don't that can cause issues that are hard to track
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.
When providing an implementation of viewWillAppear, viewDidLoad, viewDidAppear, loadView etc.
Should the calls to the superclasses corresponding methods be made before or after performing custom action?
What are some possible consequences if performed in the wrong order?
i.e.
should it be:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
stuff
}
or
- (void)viewWillAppear:(BOOL)animated
{
stuff
[super viewWillAppear:animated];
}
etc.
For the vast majority of things you'd like to do, it won't make any difference at all. It's convenient to place the call to "super" first, because then it's easy to check later an make sure you are calling super. Apple's documentation just states that "you must call super at some point in your implementation".
There is one case where it is more likely to matter. If you are not inheriting directly from UIViewController, but instead from another custom class, then you should research the specific behavior of that class in making your decision. In general, calling super first makes for a good design pattern to make it easy to always predict behavior when debugging.
In the template Apple gives you, I see that they have:
/*
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
*/
I understand that you want your super class to do any loading of the view before your own view. For viewDidDisappear, do you do the same thing for the same reason?
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
}
Yes always call the super class method first to have it complete so then you can override any behaviour subsequently in your inherited classes method.
The only exception to this is the dealloc method where you should call [super dealloc] last to make sure you've cleared up your own class properly before any references might get lost and cause a memory leak.
I would say that things should always be taken apart in the opposite order they are built up. You call [super viewWillAppear:animated] at the beginning of viewWillAppear so the super class has a chance to set up the default actions, then you get your opportunity to modify.
For the call to [super viewDidDisappear:animated] you should do your disappearing code before calling the super one at the end of your viewDidDisappear. That way if you have any references to the default view contents they are not released before you are done with them. If you don't use anything in the super class then it is probably irrelevant.