I've seen NSNotification addObserver/removeObserver placed in viewDidLoad/viewDidUnload, viewDidAppear/viewDidDisappear,dealloc`....
What are the proper methods to use here so things are neat and tidy?
That depends. Do you only want to receive notifications when your view controller is on screen (then viewWillAppear/Disappear is probably a good choice) or also when the view is not currently active. In the latter case, the init method might be even better suited than viewDidLoad (or does it matter for the notification that the view is currently in memory?).
Also note that viewDidUnload is not called in all cases (only when the view gets unloaded but the view controller remains in memory – if the view controller is deallocated while the view is loaded, only dealloc is called and is the correct place to unregister.
For viewControllers:
I would say in viewWillAppear: and viewDidDisappear:.
The reason is that you care for these notifications as long as your view is "on screen".
Since a view does not need to be unloaded when your view is offscreen viewDidLoad and viewDidUnload are the wrong locations for (de)registering notifications.
For registering for notification for non views:
In the designated initializer and dealloc.
Related
I use the following to open a new view controller
ViewController *mainMenu = [self.storyboard instantiateViewControllerWithIdentifier:#"mainview"];
[self.navigationController pushViewController:mainMenu animated:NO];
When the mainview is active on the screen the application still runs old view controller (methods) in the background.
How i can stop the old view controller from running or make it inactive?
First, let's ask ourselves this: what kind of methods run on a view controller?
I'm going to categorize the methods that are part of a UIViewController subclass into 4 categories (though there's clearly overlap among these categories).
Life-cycle methods. These are methods such as viewDidLoad, viewWillAppear:, etc. that run as part of the view controller's life-cycle. None of these methods should be running if the view is "in the background" other than, arguably, viewDidLoad, viewWillAppear: which are technically called before the view has come to the foreground, and viewDidDisappear: which is technically called just after the view has left the foreground. So we don't really need to worry about these.
Delegate methods. Your view controller could delegate objects. The delegate methods are called on your view controller whenever the object they're delegating calls them. In the majority of the cases, we're talking about delegating some sort of UI element which is part of the view controller. This UI element would be "in the background" any time the view controller is, so it shouldn't be calling delegate methods.
NSTimer called methods. NSTimer can keep a strong reference to an object, and keep that object alive until the fire time ticks over and it calls the specified method on that object. These methods are certainly likely to be called even if the view controller is in the background, but if you want to stop the timer from running if the view controller's view isn't visible, then just invalidate it in viewDidDisappear:.
Miscellaneous methods. These methods don't necessarily into any of the above categories (though they could). Most of the time these methods are called are from within the view controller themselves, so if nothing is being called from one of the first three categories, it will be rare that this category is called (unless the method is public).
So you've got methods running "in the background" on your view controller. What category do they fall in?
If they're in the first category, then either you're calling a life-cycle method manually from somewhere (which I can't recommend against enough--there's no good reason to do this), or perhaps you don't understand the UIViewController life cycle.
If they're in the second category, then it's most likely good that they're being called. If the Object A is being delegated by View Controller A and Object A is still working on some process and still requires delegation, then it's extraordinarily important that View Controller A is still responding to the delegation methods. If you don't want this to happen, you need to stop Object A from requiring delegation.
If they're in the third category, invalidate your NSTimer objects when it's appropriate. NSTimer doesn't just magically not do what you scheduled it to do. You have to invalidate it in viewDidDisappear if you don't want it to call methods on the view controller when its view is not visible.
If they're in the fourth category, start by making sure you've not got public methods that you're calling manually from outside the view controller. If you do, well stop calling them. If you don't, go double check the first three categories.
If you mean that the view controller that was active before is still executing code, I'd suggest doing the following to remove it from view and execution before pushing:
ViewController *mainMenu = [self.storyboard instantiateViewControllerWithIdentifier:#"mainview"];
[self.navigationController popViewControllerAnimated: NO]; // Takes the old one away
[self.navigationController pushViewController:mainMenu animated:NO];
I am doing an application which downloads image from server and displays it on a view. I am using delegate for that. Once the image is finished loading a delegate sends message to the view to show the image.
The above scenario is working fine. But if I move out from that particular view to any other view, when the image loading is finished the delegate tries to send message and causes an error.
I tried setting the
imageFetcher.delegate=nil;
in view didUnload. Also before calling the delegate method in download class I check for delegate is nil.
But i can see that the delegate object is not nil.
if(delegate!=nil)
{
[delegate imagefetcherView:self didLoadImage:image];
}
How can I fix this error?
Thanks
Do not rely viewDidUnload to do any cleanup. That's only called in iOS versions prior to iOS 6, and only when the view is unloaded due to memory pressure (but not when you just dismiss/pop the view).
Set your delegate to nil in the dealloc method or viewDidDisappear or wherever is appropriate.
Two caveats relevant to picking which method you'll nil the delegate:
Be aware that viewWillDisappear and viewDidDisappear will also be called if you push/present another view controller, even if the current one has not been yet been dismissed. Only rely upon these disappear-related methods if the view controller in question does not ever push/present another view controller.
If employing the dealloc technique, note that this only works if the delegate is a weak property of the image fetcher class (and delegates generally should be weak). If the delegate was a strong or retain property, that will prevent the view controller's dealloc from ever getting called.
By the way, I gather that you are letting the image fetch continue, even though the view controller has been dismissed. You might want to not only nil the delegate, but cancel the request, too. It depends upon whether (a) you're using a fetch that even permits a cancellation (e.g. a NSURLConnectionDataDelegate approach or a AFNetworking operation) and, if so, (b) whether you want it to cancel or not. It's easy, though, to tie up precious network resources (esp if on a slow cellular connection) letting requests continue even if the user doesn't need it anymore. It depends upon the particulars of your app.
Regardless, do not rely upon viewDidUnload.
viewDidUnload isnt called in iOS 6+.
you should use this
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
imageFetcher.delegate=nil;
}
You still can have a dealloc call in your class but it should not call [super dealloc]. If you add it you can set up a breakpoint here and see from where its gets its retain count to 0. Or use Instruments to track retain/release cycle of your controller.
I would implement a separate cache to temporarily store the picture in care view controller is deallocated but the picture can be used once again, e.g. if the user gets back to the same page.
In that case you would have a long-lived cache object as a delegate. View controllers can, for example, subscribe to receive key-value notifications about incoming pictures when those view controllers become visible (just don't forget to unsubscribe from KVO in viewWillDisappear).
If your controller is invisible but likely to be shown again you'll have the picture in cache (to be dropped if low memory); of course you can also check in the cache if your picture is never likely to be shown again and drop the picture.
I've got a question regarding the two mentioned methods, since in my tests I don´t make clear the order they are called. I thought that, firstly, viewDidLoad is called when the viewController is loaded for first time (as the name indicates), and inmediately after the init method. Then, I thought that once viewDidLoad returns, viewWillAppear is called. If you display another viewController, and then you return to this one, then it should be already loaded and only viewWillAppear will be called.
However, while developing I make the impression that there is no order when calling viewDidLoad and viewWillAppear... I couldn´t find a clear description of this lifecycle in Apple's documentation, how does this actually work?
Thanks!
I would like to add to Caleb's answer: Don't confuse the view controller and the view! The name viewDidLoad clearly indicates that the method is invoked after the view has been loaded. It is the view controller that does the loading.
Some pointers regarding the lifecycle of views and the order in which messages are sent:
Not an official Apple document, but I find this diagram really useful because it includes pretty much all of UIViewController's lifecycle overrides.
In the section Resource Management in View Controllers from Apple's "View Controller Programming Guide" there is a flowchart that depicts how views are initially loaded. It explains loadView and viewDidLoad, also in conjunction with storyboards.
The section Responding to Display-Related Notifications from Apple's "View Controller Programming Guide" explains how to respond to views appearing and disappearing (viewWillAppear: et al)
If you are planning on implementing a container view controller: The UIViewController class reference has a good overview of how messages need to be sent by your subclass.
I'm stopping here. You can find more stuff yourself by googling for "uiviewcontroller life cycle".
-viewDidLoad is called when the controller loads its view, which is not necessarily right after initialization. View controllers don't load their views until they need them, either to display or for any other reason.
-viewWillAppear is called just before the view is displayed. This will be after -viewDidLoad, but you don't know exactly how long after. -viewWillAppear is called every time the view is displayed; -viewDidLoad will only be called a second time if the view is unloaded at some point (such as didReceiveMemoryWarning). These days that's unusual, but it can happen.
Or if the viewController is set to nil, which can usually happen if a view controller is kicked off the navigation stack, and therefore next time it is brought to the navigation stack it needs to call -viewDidLoad again.
I thought that, firstly, viewDidLoad is called when the viewController
is loaded for first time (as the name indicates), and inmediately after the init method
No. The name indicates that the controller's view has been loaded (not the controller itself). Actually the docs state that this method will get called after the view hierarchy has been loaded into memory (either via loadView or through a nib for example).
Then, I thought that once viewDidLoad returns, viewWillAppear is
called
Again, no. loadView (and as a consequence viewDidLoad) method will get called the first time that view property is to be accessed and is nil (which is the case when you're initializing a controller). Think of this simple scenario:
MyViewController *vc = [[MyViewController alloc] init];
UIView *view = vc.view; // <= loadView & viewDidLoad will fire but it certainly didn't appear...
However, while developing I make the impression that there is no order
when calling viewDidLoad and viewWillAppear...
Well there is an order. We know for sure that viewWillAppear will always be called after viewDidLoad (if both of them are to be called of course).
As you said, ViewDidLoad is only calling once after loading the view. So we can initialize the instances in the viewDidLoad. It is mainly meant for the initialization.
viewWillAppear will invoke whenever we reach to this view. So if there is any changes in UI, we can done it in viewWillAppear.
I ran a trace on when all these calls are made: http://thecodist.com/article/ios_arc_storyboards_and_uiviewcontroller_trace
I'm wondering if there's any way -viewWillAppear: would be called without a matching -viewDidAppear:. Ditto for -viewWillDisappear and -viewDidDisappear.
The root of my question is where to register and unregister for KVO and / or NSNotifications of an object who's change notifications will cause the view controller to update views.
For example, I have a model object that is being processed asynchronously and it's string properties could change. I'd like the view controller to KVO those properties and have any changes reflected by swapping out the text of a label managed by said view controller.
Where do you register and unregister for notifications and why?
EDIT:
A gotcha I've come across is what to do in cases of application state change (e.g. -applicationWillResignActive, -...didEnterBackground, etc). These changes don't seem to trigger view controller lifecycle methods. Any best practices here?
With the standard container view controllers, you will always get will/did messages in pairs. If you have written your own view controller container, you may not, but that would be a bug in the container implementation.
Most of the time, you'll want to set things up and tear things down in the 'will' messages. This gives your view controller the earliest shot at anything it needs to do before it becomes "active" and also shuts down things as early as possible when you no longer need them.
When pushing a view controller in nav stack, it is entirely possible that a notification will occur during the push animation. If you set up the observers in viewDidAppear, you would miss that notification. You want to be listening as soon as possible.
Likewise, I would reckon that viewDidDisappear is too late to remove callbacks. For example, a location manager could be stopped in viewDidDisappear, but another location update could be delivered during the disappearing animation. That probably doesn't hurt much, but depending on the application, something weird could happen like an alert view appearing after you've already left a view controller - which looks like a flicker to the user.
Anything non-view related, as indicated above, occur in the 'will' methods. The choice about will vs did, then, is really about what the user sees. Animations should start in viewDidAppear, otherwise, the user won't see the frames that occur during will/did appear. Data should be moved to views in viewWillAppear, otherwise, a blank view will transition in and the data will only appear after the transition animation completes. Also, it possible that a view frames could be adjusted in between viewWillAppear/viewDidAppear, like in the case of a previous view controller in the stack hiding/showing the navigation bar.
And on a side note, not something I will get into great detail here with, but I'd advocate against KVO for controller interactions that move data from model to view objects. Difficult to test and difficult to trace.
You can subclass your UILabel and in your subclass override the setText method:
-(void)setText:(NSString *)newText {
//do your KVO updates here
[super setText:newText];
}
hope this helps
I have a custom UIViewController subclass that handles all the view initialization by itself (it doesn't use nib). There is also another UIViewController subclass loaded from nib. Both are contained by UITabBarController.
When a memory warning comes, the first controller does receive notification, but viewDidUnload doesn't get called. The second controller also receives notification and it's viewDidUnload does get called.
I checked in didReceiveMemoryWarning, self.isViewLoaded is TRUE and self.view.superview is null.
Both controllers (their tabs) are invisible at the time the notification appears.
Is there something special a custom view controller should do to be unloaded in a result of memory warnings?
If you are subclassing UIViewContoller and you do not initialize it from a NIB, you need to subclass the -loadView method. Otherwise iOS assumes that the view cannot be unloaded / reloaded.
It would be sufficient to just add the following to your implementation:
- (void)loadView {
[super loadView];
}
I will try to find a documentation quote for that.
The documentation is unfortunately not very clear on this matter.
I would check out the documentation on the View Controller Lifecycle. Specifically, check out the section on what happens when the memory warning is received. If your custom view controller's view is the view on screen, the OS will not attempt to release this view. Is this view on screen when you're getting the memory warning? In the simulator, navigate to the nib-loaded view and simulate a memory warning, see if your custom view gets released then. Also, see if viewWillUnload is being called. And make sure that in any of these methods that you're overriding that you call super.